1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372(*
* lTerm_read_line.ml
* ------------------
* Copyright : (c) 2011, Jeremie Dimino <jeremie@dimino.org>
* Licence : BSD3
*
* This file is a part of Lambda-Term.
*)openCamomileLibraryDefault.CamomileopenLwt_reactopenLTerm_geomopenLTerm_styleopenLTerm_keyletreturn,(>>=)=Lwt.return,Lwt.(>>=)typeprompt=LTerm_text.ttypehistory=Zed_string.tlist(* +-----------------------------------------------------------------+
| Completion |
+-----------------------------------------------------------------+ *)letcommon_prefix_oneab=letrecloopofs=ifofs=String.lengtha||ofs=String.lengthbthenString.suba0ofselseletch1,ofs1=Zed_utf8.unsafe_extract_nextaofsandch2,ofs2=Zed_utf8.unsafe_extract_nextbofsinifch1=ch2&&ofs1=ofs2thenloopofs1elseString.suba0ofsinloop0letcommon_prefix=function|[]->""|word::rest->List.fold_leftcommon_prefix_onewordrestletzed_common_prefix_oneab=letrecloopofs=ifofs=Zed_string.bytesa||ofs=Zed_string.bytesbthenZed_string.sub_ofs~ofs:0~len:ofsaelseletch1,ofs1=Zed_string.extract_nextaofsandch2,ofs2=Zed_string.extract_nextbofsinifch1=ch2&&ofs1=ofs2thenloopofs1elseZed_string.sub_ofs~ofs:0~len:ofsainloop0letzed_common_prefix=function|[]->Zed_string.empty()|word::rest->List.fold_leftzed_common_prefix_onewordrestletlookupwordwords=List.filter(funword'->Zed_utf8.starts_withword'word)wordsletlookup_assocwordwords=List.filter(fun(word',_)->Zed_utf8.starts_withword'word)wordsincludeLTerm_read_line_basemoduleBindings=Zed_input.Make(LTerm_key)letbindings=refBindings.emptyletbindseqactions=bindings:=Bindings.addseqactions!bindingsletunbindseq=bindings:=Bindings.removeseq!bindingslet()=bind[{control=false;meta=false;shift=false;code=Home}][Edit(LTerm_edit.ZedZed_edit.Goto_bot)];bind[{control=false;meta=false;shift=false;code=End}][Edit(LTerm_edit.ZedZed_edit.Goto_eot)];bind[{control=false;meta=false;shift=false;code=Up}][History_prev];bind[{control=false;meta=false;shift=false;code=Down}][History_next];bind[{control=false;meta=false;shift=false;code=Tab}][Complete];bind[{control=false;meta=false;shift=false;code=Enter}][Accept];bind[{control=true;meta=false;shift=false;code=Char(UChar.of_char'b')}][Edit(LTerm_edit.ZedZed_edit.Prev_char)];bind[{control=true;meta=false;shift=false;code=Char(UChar.of_char'f')}][Edit(LTerm_edit.ZedZed_edit.Next_char)];bind[{control=true;meta=false;shift=false;code=Char(UChar.of_char'h')}][Edit(LTerm_edit.ZedZed_edit.Delete_prev_char)];bind[{control=true;meta=false;shift=false;code=Char(UChar.of_char'c')}][Break];bind[{control=true;meta=false;shift=false;code=Char(UChar.of_char'z')}][Suspend];bind[{control=true;meta=false;shift=false;code=Char(UChar.of_char'm')}][Accept];bind[{control=true;meta=false;shift=false;code=Char(UChar.of_char'l')}][Clear_screen];bind[{control=true;meta=false;shift=false;code=Char(UChar.of_char'r')}][Prev_search];bind[{control=true;meta=false;shift=false;code=Char(UChar.of_char's')}][Next_search];bind[{control=true;meta=false;shift=false;code=Char(UChar.of_char'd')}][Interrupt_or_delete_next_char];bind[{control=false;meta=true;shift=false;code=Char(UChar.of_char'p')}][History_prev];bind[{control=false;meta=true;shift=false;code=Char(UChar.of_char'n')}][History_next];bind[{control=false;meta=true;shift=false;code=Left}][Complete_bar_prev];bind[{control=false;meta=true;shift=false;code=Right}][Complete_bar_next];bind[{control=false;meta=true;shift=false;code=Home}][Complete_bar_first];bind[{control=false;meta=true;shift=false;code=End}][Complete_bar_last];bind[{control=false;meta=true;shift=false;code=Tab}][Complete_bar];bind[{control=false;meta=true;shift=false;code=Down}][Complete_bar];bind[{control=false;meta=true;shift=false;code=Enter}][Edit(LTerm_edit.ZedZed_edit.Newline)];bind[{control=false;meta=false;shift=false;code=Escape}][Cancel_search];bind[{control=true;meta=false;shift=false;code=Char(UChar.of_char'x')};{control=true;meta=false;shift=false;code=Char(UChar.of_char'e')}][Edit_with_external_editor](* +-----------------------------------------------------------------+
| The read-line engine |
+-----------------------------------------------------------------+ *)letis_prefix~prefixs=letprefix=Zed_string.to_utf8prefixands=Zed_string.to_utf8sinString.lengthprefix<=String.lengths&&(leti=ref0inwhile!i<String.lengthprefix&&s.[!i]=prefix.[!i]doincridone;!i=String.lengthprefix)lethistory_findpredicatehistory=letrechistory_find_skipped=function|[]->None|x::xs->ifpredicatexthenSome(skipped,x,xs)elsehistory_find_(x::skipped)xsinhistory_find_[]historyletsearch_stringstrsub=letstr=Zed_string.to_utf8strandsub=Zed_string.to_utf8subinletrecequal_atab=(b=String.lengthsub)||(String.unsafe_getstra=String.unsafe_getsubb)&&equal_at(a+1)(b+1)inletrecloopofsidx=ifofs+String.lengthsub>String.lengthstrthenNoneelseifequal_atofs0thenSomeidxelseloop(Zed_utf8.unsafe_nextstrofs)(idx+1)inloop00letmacro=Zed_macro.create[]typemode=|Edition|Search|Set_counter|Add_countertypecompletion_state={start:int;(* Beginning of the word being completed *)index:int;(* Index of the selected in [words] *)count:int;(* Length of [words] *)words:(Zed_string.t*Zed_string.t)list;}letno_completion={start=0;index=0;words=[];count=0;}typedirection=Forward|Backwardtypesearch_status={before:Zed_string.tlist;after:Zed_string.tlist;match_:(Zed_string.t*int)option}classvirtual['a]engine?(history=[])?(clipboard=LTerm_edit.clipboard)?(macro=macro)()=letedit:unitZed_edit.t=Zed_edit.create~clipboard()inletcontext=Zed_edit.contextedit(Zed_edit.new_cursoredit)inletmode,set_mode=S.createEditioninletuser_completion_state,set_completion_state=E.create()inletreset_completion_state=E.when_(S.map(funmode->mode=Edition)mode)(E.select[E.stamp(Zed_edit.changesedit)no_completion;E.stamp(S.changes(Zed_cursor.position(Zed_edit.cursorcontext)))no_completion;])inletcompletion_state=S.hold~eq:(==)no_completion(E.select[reset_completion_state;user_completion_state])inletcompletion_words=S.map~eq:(==)(func->c.words)completion_stateinletcompletion_index=S.map(func->c.index)completion_stateinlethistory,set_history=S.create(history,[])inletmessage,set_message=S.createNoneinlethistory_prefix,set_history_prefix=letev,send=E.create()inletedit_changes=Zed_edit.changeseditinletedit_changes=E.map(fun_->Zed_edit.textedit)edit_changesinletprefix=S.hold(Zed_rope.empty())(E.select[ev;edit_changes])inprefix,sendinobject(self)methodvirtualeval:'amethodedit=editmethodcontext=contextmethodshow_box=truemethodmode=modemethodhistory=historymethodmessage=messagemethodclipboard=clipboardmethodmacro=macrovalinterrupt:exnLwt_mvar.t=Lwt_mvar.create_empty()methodinterrupt=interrupt(* The event which occurs when completion need to be recomputed. *)valmutablecompletion_event=E.never(* Save for when setting the macro counter. *)valmutablesave=(0,Zed_rope.empty())methodset_completion?(index=0)startwords=letcount=List.lengthwordsinifindex<0||index>max0(count-1)theninvalid_arg"LTerm_read_line.set_completion: \
index out of bounds compared to words.";set_completion_state{start;index;count;words}initializercompletion_event<-(E.map(fun_->(* We can't execute it right now as the user might call [set_completion]
immediatly. *)Lwt.pause()>>=fun()->self#completion;Lwt.return_unit)reset_completion_state);self#completionmethodinput_prev=Zed_rope.before(Zed_edit.textedit)(Zed_edit.positioncontext)methodinput_next=Zed_rope.after(Zed_edit.textedit)(Zed_edit.positioncontext)methodcompletion_words=completion_wordsmethodcompletion_index=completion_indexmethodcompletion=self#set_completion0[]methodcomplete=letcomp=S.valuecompletion_stateinletprefix_length=Zed_edit.positioncontext-comp.startinmatchcomp.wordswith|[]->()|[(completion,suffix)]->Zed_edit.insertcontext(Zed_rope.of_string(Zed_string.subcompletion~pos:prefix_length~len:(Zed_string.lengthcompletion-prefix_length)));Zed_edit.insertcontext(Zed_rope.of_stringsuffix)|(completion,_suffix)::rest->letword=List.fold_left(funacc(word,_)->zed_common_prefix_oneaccword)completionrestinZed_edit.insertcontext(Zed_rope.of_string(Zed_string.subword~pos:prefix_length~len:(Zed_string.lengthword-prefix_length)))(* The event which search for the string in the history. *)valmutablesearch_event=E.nevervalmutablesearch_status=Noneinitializerletreset_search_=search_status<-None;self#searchBackwardinsearch_event<-E.mapreset_search(E.when_(S.map(funmode->mode=Search)mode)(Zed_edit.changesedit))methodprivatesearchdirection=letdo_searchdirection=letset_statusother_entriesentriesmatch_=letbefore,after=matchdirectionwith|Backward->(other_entries,entries)|Forward->(entries,other_entries)insearch_status<-Some{before;after;match_}inletinput=Zed_rope.to_string(Zed_edit.textedit)inletrecloopother_entriesentries=matchentrieswith|[]->set_statusother_entriesentriesNone;set_message(Some(LTerm_text.of_utf8"Reverse search: not found"))|entry::rest->matchsearch_stringentryinputwith|Somepos->beginmatchsearch_statuswith|Some{match_=Some(entry',_);_}whenentry=entry'->loop(entry::other_entries)rest|_->set_statusother_entriesrest(Some(entry,pos));lettxt=LTerm_text.of_stringentryinfori=postopos+Zed_rope.length(Zed_edit.textedit)-1doletch,style=txt.(i)intxt.(i)<-(ch,{stylewithunderline=Sometrue})done;set_message(Some(Array.append(LTerm_text.of_utf8"Reverse search: ")txt))end|None->loop(entry::other_entries)restinmatchsearch_statuswith|None->lethist=fst(S.valuehistory)inloop[](matchdirectionwith|Backward->hist|Forward->List.revhist)|Some{before;after;match_}->letother_entries,entries=matchdirectionwith|Backward->(before,after)|Forward->(after,before)inletother_entries=matchmatch_with|None->other_entries|Some(entry,_)->entry::other_entriesinloopother_entriesentriesinmatchS.valuemodewith|Search->do_searchdirection|Edition->lettext=Zed_edit.texteditinZed_edit.gotocontext0;Zed_edit.removecontext(Zed_rope.lengthtext);letprev,next=S.valuehistoryinset_history(Zed_rope.to_stringtext::(List.rev_appendnextprev),[]);search_status<-None;set_modeSearch;do_searchdirection|_->()methodinsertch=Zed_edit.insert_charcontextchmethodsend_actionaction=ifaction<>EditLTerm_edit.Stop_macrothenZed_macro.addmacroaction;matchactionwith|(Complete|Complete_bar|Accept)whenS.valuemode=Search->beginset_modeEdition;set_messageNone;matchsearch_statuswith|Some{match_=Some(entry,_pos);_}->search_status<-None;Zed_edit.gotocontext0;Zed_edit.removecontext(Zed_rope.length(Zed_edit.textedit));Zed_edit.insertcontext(Zed_rope.of_stringentry)|Some{match_=None;_}|None->()end|Edit(LTerm_edit.Zedaction)->Zed_edit.get_actionactioncontext|Interrupt_or_delete_next_char->ifZed_rope.is_empty(Zed_edit.textedit)thenLwt.async(fun()->Lwt_mvar.putinterruptInterrupt)elseZed_edit.delete_next_charcontext|CompletewhenS.valuemode=Edition->self#complete|Complete_bar_nextwhenS.valuemode=Edition->letcomp=S.valuecompletion_stateinifcomp.index<comp.count-1thenset_completion_state{compwithindex=comp.index+1}|Complete_bar_prevwhenS.valuemode=Edition->letcomp=S.valuecompletion_stateinifcomp.index>0thenset_completion_state{compwithindex=comp.index-1}|Complete_bar_firstwhenS.valuemode=Edition->letcomp=S.valuecompletion_stateinifcomp.index>0thenset_completion_state{compwithindex=0}|Complete_bar_lastwhenS.valuemode=Edition->letcomp=S.valuecompletion_stateinifcomp.index<comp.count-1thenset_completion_state{compwithindex=comp.count-1}|Complete_barwhenS.valuemode=Edition->letcomp=S.valuecompletion_stateinifcomp.words<>[]thenbeginletprefix_length=Zed_edit.positioncontext-comp.startinletcompletion,suffix=List.nthcomp.wordscomp.indexinZed_edit.insertcontext(Zed_rope.of_string(Zed_string.aftercompletionprefix_length));Zed_edit.insertcontext(Zed_rope.of_stringsuffix)end|History_prevwhenS.valuemode=Edition->beginletprev,next=S.valuehistoryinmatchprevwith|[]->()|line::rest->lettext=Zed_edit.texteditinset_history(rest,Zed_rope.to_stringtext::next);Zed_edit.gotocontext0;Zed_edit.removecontext(Zed_rope.lengthtext);Zed_edit.insertcontext(Zed_rope.of_stringline)end|History_nextwhenS.valuemode=Edition->beginletprev,next=S.valuehistoryinmatchnextwith|[]->()|line::rest->lettext=Zed_edit.texteditinset_history(Zed_rope.to_stringtext::prev,rest);Zed_edit.gotocontext0;Zed_edit.removecontext(Zed_rope.lengthtext);Zed_edit.insertcontext(Zed_rope.of_stringline)end|History_search_prevwhenS.valuemode=Edition->beginletprev,next=S.valuehistoryinlettext=Zed_rope.to_string@@Zed_edit.texteditinletprefix=S.valuehistory_prefixinmatchhistory_find(is_prefix~prefix:(Zed_rope.to_stringprefix))prevwith|None->()|Some(not_matched,line,rest)->set_history(rest,not_matched@text::next);Zed_edit.gotocontext0;Zed_edit.delete_next_linecontext;Zed_edit.insertcontext(Zed_rope.of_stringline);set_history_prefixprefixend|History_search_nextwhenS.valuemode=Edition->beginletprev,next=S.valuehistoryinletprefix=S.valuehistory_prefixinmatchhistory_find(is_prefix~prefix:(Zed_rope.to_stringprefix))nextwith|None->()|Some(not_matched,line,rest)->lettext=Zed_rope.to_string@@Zed_edit.texteditinset_history(not_matched@text::prev,rest);Zed_edit.gotocontext0;Zed_edit.delete_next_linecontext;Zed_edit.insertcontext(Zed_rope.of_stringline);set_history_prefixprefixend|Prev_search->self#searchBackward|Next_search->self#searchForward|Cancel_search->ifS.valuemode=Searchthenbeginset_modeEdition;set_messageNoneend|EditLTerm_edit.Start_macrowhenS.valuemode=Edition->Zed_macro.set_recordingmacrotrue|EditLTerm_edit.Stop_macro->Zed_macro.set_recordingmacrofalse|EditLTerm_edit.Cancel_macro->Zed_macro.cancelmacro|EditLTerm_edit.Play_macro->Zed_macro.cancelmacro;List.iterself#send_action(Zed_macro.contentsmacro)|EditLTerm_edit.Insert_macro_counter->Zed_edit.insertcontext(Zed_rope.of_string(Zed_string.unsafe_of_utf8(string_of_int(Zed_macro.get_countermacro))));Zed_macro.add_countermacro1|EditLTerm_edit.Set_macro_counterwhenS.valuemode=Edition->lettext=Zed_edit.texteditinsave<-(Zed_edit.positioncontext,text);Zed_edit.gotocontext0;Zed_edit.removecontext(Zed_rope.lengthtext);set_modeSet_counter;set_message(Some(LTerm_text.of_utf8"Enter a value for the macro counter."))|EditLTerm_edit.Add_macro_counterwhenS.valuemode=Edition->lettext=Zed_edit.texteditinsave<-(Zed_edit.positioncontext,text);Zed_edit.gotocontext0;Zed_edit.removecontext(Zed_rope.lengthtext);set_modeAdd_counter;set_message(Some(LTerm_text.of_utf8"Enter a value to add to the macro counter."))|Accept->beginmatchS.valuemodewith|Edition|Search->()|Set_counter->letpos,text=saveinsave<-(0,Zed_rope.empty());(tryZed_macro.set_countermacro(int_of_string(Zed_string.to_utf8(Zed_rope.to_string(Zed_edit.textedit))))withFailure_->());Zed_edit.gotocontext0;Zed_edit.removecontext(Zed_rope.length(Zed_edit.textedit));Zed_edit.insertcontexttext;Zed_edit.gotocontextpos;set_modeEdition;set_messageNone|Add_counter->letpos,text=saveinsave<-(0,Zed_rope.empty());(tryZed_macro.add_countermacro(int_of_string(Zed_string.to_utf8(Zed_rope.to_string(Zed_edit.textedit))))withFailure_->());Zed_edit.gotocontext0;Zed_edit.removecontext(Zed_rope.length(Zed_edit.textedit));Zed_edit.insertcontexttext;Zed_edit.gotocontextpos;set_modeEdition;set_messageNoneend|Break->raiseSys.Break|Edit(LTerm_edit.Customf)->f()|_->()methodstyliselast=lettxt=LTerm_text.of_rope(Zed_edit.textedit)inletpos=Zed_edit.positioncontextinifnotlast&&Zed_edit.get_selectioneditthenbeginletmark=Zed_cursor.get_position(Zed_edit.markedit)inleta=minposmarkandb=maxposmarkinfori=atob-1doletch,style=txt.(i)intxt.(i)<-(ch,{stylewithunderline=Sometrue})done;end;(txt,pos)endclassvirtual['a]abstract=objectmethodvirtualeval:'amethodvirtualsend_action:action->unitmethodvirtualinsert:UChar.t->unitmethodvirtualedit:unitZed_edit.tmethodvirtualcontext:unitZed_edit.contextmethodvirtualclipboard:Zed_edit.clipboardmethodvirtualmacro:actionZed_macro.tmethodvirtualstylise:bool->LTerm_text.t*intmethodvirtualhistory:(Zed_string.tlist*Zed_string.tlist)signalmethodvirtualmessage:LTerm_text.toptionsignalmethodvirtualinput_prev:Zed_rope.tmethodvirtualinput_next:Zed_rope.tmethodvirtualcompletion_words:(Zed_string.t*Zed_string.t)listsignalmethodvirtualcompletion_index:intsignalmethodvirtualset_completion:?index:int->int->(Zed_string.t*Zed_string.t)list->unitmethodvirtualcompletion:unitmethodvirtualcomplete:unitmethodvirtualshow_box:boolmethodvirtualmode:modesignalmethodvirtualinterrupt:exnLwt_mvar.tend(* +-----------------------------------------------------------------+
| Predefined classes |
+-----------------------------------------------------------------+ *)classread_line?history()=object(self)inherit[Zed_string.t]engine?history()methodeval=Zed_rope.to_string(Zed_edit.textself#edit)endclassread_password()=object(self)inherit[Zed_string.t]engine()assupermethod!styliselast=lettext,pos=super#styliselastinfori=0toArray.lengthtext-1dolet_ch,style=text.(i)intext.(i)<-(Zed_char.unsafe_of_char'*',style)done;(text,pos)methodeval=Zed_rope.to_string(Zed_edit.textself#edit)method!show_box=falsemethod!send_action=function|Prev_search|Next_search->()|action->super#send_actionactionendtype'aread_keyword_result=|Rk_valueof'a|Rk_errorofZed_string.tclass['a]read_keyword?history()=object(self)inherit['aread_keyword_result]engine?history()methodkeywords=[]methodeval=letinput=Zed_rope.to_string(Zed_edit.textself#edit)intryRk_value(List.associnputself#keywords)withNot_found->Rk_errorinputmethod!completion=letword=Zed_rope.to_stringself#input_previnletkeywords=List.filter(fun(keyword,_value)->Zed_string.starts_with~prefix:wordkeyword)self#keywordsinself#set_completion0(List.map(fun(keyword,_value)->(keyword,Zed_string.empty()))keywords)end(* +-----------------------------------------------------------------+
| Running in a terminal |
+-----------------------------------------------------------------+ *)letnewline_uChar=UChar.of_char'\n'letnewline=Zed_char.unsafe_of_uChar@@newline_uCharletvline=LTerm_draw.({top=Light;bottom=Light;left=Blank;right=Blank})letreverse_style={LTerm_style.nonewithLTerm_style.reverse=Sometrue}letdefault_prompt=LTerm_text.of_utf8"# "letrecdropcountl=ifcount<=0thenlelsematchlwith|[]->[]|_::l->drop(count-1)l(* Computes the position of the cursor after printing the given styled
string:
- [pos] is the current cursor position
(it may be at column [max-column + 1])
- [text] is the text to display
- [start] is the start of the chunk to display in [text]
- [stop] is the end of the chunk to display in [text]
*)letreccompute_positioncolspostextstartstop=ifstart=stopthenposelseletch,_style=text.(start)inifch=newlinethencompute_positioncols{row=pos.row+1;col=0}text(start+1)stopelseletwidth=Zed_char.widthchinifpos.col+width>colsthencompute_positioncols{row=pos.row+1;col=width}text(start+1)stopelsecompute_positioncols{poswithcol=pos.col+max0width}text(start+1)stop(* Return the "real" position of the cursor, i.e. on the screen. *)letreal_poscolspos=ifpos.col=colsthen{row=pos.row+1;col=0}elseposletrecget_index_of_last_displayed_wordcolumncolumnsindexwords=matchwordswith|[]->index-1|(word,_suffix)::words->letcolumn=column+Zed_string.lengthwordinifcolumn<=columns-1thenget_index_of_last_displayed_word(column+1)columns(index+1)wordselseindex-1(*let rec get_index_of_last_displayed_word_by_width column columns index words =
match words with
| [] ->
index - 1
| (word, _suffix) :: words ->
let column = column + Zed_string.(aval_width (width word)) in
if column <= columns - 1 then
get_index_of_last_displayed_word_by_width (column + 1) columns (index + 1) words
else
index - 1*)letdraw_styledctxrowcolstr=letsize=LTerm_draw.sizectxinletreclooprowcolidx=ifidx<Array.lengthstrthenbeginletch,style=Array.unsafe_getstridxinifch=newlinethenloop(row+1)0(idx+1)elsebeginletwidth=max1(Zed_char.widthch)inifcol+width>size.colsthenloop(row+1)0idxelsebeginLTerm_draw.draw_charctxrowcol~stylech;looprow(col+width)(idx+1)endendendinlooprowcol0letdraw_styled_with_newlinesmatrixcolsrowcolstr=letreclooprowcolidx=ifidx<Array.lengthstrthenbeginletch,style=Array.unsafe_getstridxinifch=newlinethenbeginLTerm_draw.draw_char_matrixmatrixrowcolnewline;loop(row+1)0(idx+1)endelsebeginletwidth=max1(Zed_char.widthch)inifcol+width>colsthenloop(row+1)0idxelsebeginLTerm_draw.draw_char_matrixmatrixrowcol~stylech;looprow(col+width)(idx+1)endendendinlooprowcol0letstyled_newline=[|(newline,LTerm_style.none)|]classvirtual['a]termterm=letsize,set_size=S.create(LTerm.sizeterm)inletevent,set_prompt=E.create()inleteditor_mode,set_editor_mode=S.createLTerm_editor.Defaultinletprompt=S.switch(S.hold~eq:(==)(S.constdefault_prompt)event)inletkey_sequence,set_key_sequence=S.create[]inobject(self)inherit['a]abstractmethodsize=sizemethodprompt=promptmethodset_promptprompt=set_promptpromptvalmutablevisible=true(* Whether the read-line instance is currently visible. *)valmutabledisplayed=false(* Whether the read-line instance is currently displayed on the
screen. *)valmutabledraw_queued=false(* Whether a draw operation has been queued, in which case it is
not necessary to redraw. *)valmutablecursor={row=0;col=0}(* The position of the cursor. *)valmutablecompletion_start=S.const0(* Index of the first displayed word in the completion bar. *)valmutableheight=0(* The height of the displayed material. *)valmutableresolver=None(* The current resolver for resolving input sequences. *)valmutablerunning=truevalvi_state=newLTerm_vi.statevalmutablevi_edit=Noneinitializercompletion_start<-(S.fold(funstart(words,index,columns)->ifindex<startthen(* The cursor is before the left margin. *)letcount=List.lengthwordsinletrev_index=count-index-1incount-get_index_of_last_displayed_word1columnsrev_index(droprev_index(List.revwords))-1elseifindex>get_index_of_last_displayed_word1columnsstart(dropstartwords)then(* The cursor is after the right margin. *)indexelsestart)0(S.changes(S.l3(funwordsindexsize->(words,index,size.cols))self#completion_wordsself#completion_indexsize)))methodeditor_mode=editor_modevalmutablevi_thread=Nonemethodvi_state=vi_statemethodset_editor_modemode=set_editor_modemode;matchmodewith|LTerm_editor.Default->vi_edit<-None;(matchvi_threadwith|Somethread->LTerm_vi.Concurrent.Thread.cancelthread;vi_thread<-None;|None->());|LTerm_editor.Vi->let_vi_edit=vi_state#vi_editinvi_edit<-Some_vi_edit;self#listen_vi_vi_editself#interruptmethodkey_sequence=key_sequencemethodcompletion_start=completion_startvaldraw_mutex=Lwt_mutex.create()methodprivatequeue_draw_update=ifdraw_queuedthenreturn()elsebegin(* Wait a bit in order not to draw too often. *)draw_queued<-true;Lwt.pause()>>=fun()->draw_queued<-false;Lwt_mutex.with_lockdraw_mutex(fun()->ifrunningthenself#draw_updateelsereturn())endmethoddraw_update=letsize=S.valuesizeinifvisible&&size.rows>0&&size.cols>0thenbeginletstyled,position=self#stylisefalseinletprompt=S.valuepromptin(* Compute the position of the cursor after displaying the
prompt. *)letpos_after_prompt=compute_positionsize.cols{row=0;col=0}prompt0(Array.lengthprompt)in(* Compute the position of the cursor after displaying the
input before the cursor. *)letpos_after_before=compute_positionsize.colspos_after_promptstyled0positionin(* Compute the position of the cursor after displaying the
input. *)letpos_after_styled=compute_positionsize.colspos_after_beforestyledposition(Array.lengthstyled)in(* Compute the position of the cursor after displaying the
newline used to end the input. *)letpos_after_newline=compute_positionsize.colspos_after_styledstyled_newline01in(* The real position of the cursor on the screen. *)letpos_cursor=real_possize.colspos_after_beforein(* Height of prompt+input. *)letprompt_input_height=max(pos_cursor.row+1)pos_after_newline.rowinletmatrix=ifself#show_box&&size.cols>2thenmatchS.valueself#messagewith|Somemsg->(* Compute the height of the message. *)letmessage_height=(compute_position(size.cols-2){row=0;col=0}msg0(Array.lengthmsg)).row+1in(* The total height of the displayed text. *)lettotal_height=prompt_input_height+message_height+2in(* Create the matrix for the rendering. *)letmatrix_size={cols=size.cols+1;rows=ifdisplayedthenmaxtotal_heightheightelsetotal_height}inletmatrix=LTerm_draw.make_matrixmatrix_sizein(* Update the height parameter. *)height<-total_height;(* Draw the prompt and the input. *)draw_styled_with_newlinesmatrixsize.cols00prompt;draw_styled_with_newlinesmatrixsize.colspos_after_prompt.rowpos_after_prompt.colstyled;draw_styled_with_newlinesmatrixsize.colspos_after_styled.rowpos_after_styled.colstyled_newline;letctx=LTerm_draw.sub(LTerm_draw.contextmatrixmatrix_size){row1=0;col1=0;row2=matrix_size.rows;col2=size.cols;}in(* Draw a frame for the message. *)LTerm_draw.draw_framectx{row1=prompt_input_height;col1=0;row2=total_height;col2=size.cols;}LTerm_draw.Light;forrow=prompt_input_heighttototal_height-1doLTerm_draw.draw_char_matrixmatrixrowsize.colsnewline;done;(* Draw the message. *)letctx=LTerm_draw.subctx{row1=prompt_input_height+1;col1=1;row2=total_height-1;col2=size.cols-1;}indraw_styledctx00msg;matrix|None->letcomp_start=S.valueself#completion_startinletcomp_index=S.valueself#completion_indexinletcomp_words=dropcomp_start(S.valueself#completion_words)in(* The total height of the displayed text. *)lettotal_height=prompt_input_height+3in(* Create the matrix for the rendering. *)letmatrix_size={cols=size.cols+1;rows=ifdisplayedthenmaxtotal_heightheightelsetotal_height}inletmatrix=LTerm_draw.make_matrixmatrix_sizein(* Update the height parameter. *)height<-total_height;(* Draw the prompt and the input. *)draw_styled_with_newlinesmatrixsize.cols00prompt;draw_styled_with_newlinesmatrixsize.colspos_after_prompt.rowpos_after_prompt.colstyled;draw_styled_with_newlinesmatrixsize.colspos_after_styled.rowpos_after_styled.colstyled_newline;letctx=LTerm_draw.sub(LTerm_draw.contextmatrixmatrix_size){row1=0;col1=0;row2=matrix_size.rows;col2=size.cols;}in(* Draw a frame for the completion. *)LTerm_draw.draw_framectx{row1=prompt_input_height;col1=0;row2=total_height;col2=size.cols;}LTerm_draw.Light;forrow=prompt_input_heighttototal_height-1doLTerm_draw.draw_char_matrixmatrixrowsize.colsnewline;done;(* Draw the completion. *)letctx=LTerm_draw.subctx{row1=prompt_input_height+1;col1=1;row2=total_height-1;col2=size.cols-1;}inletrecloopidxcol=function|[]->()|(word,_suffix)::words->letlen=Zed_string.lengthwordinLTerm_draw.draw_stringctx0colword;(* Apply the reverse style if this is the selected word. *)ifidx=comp_indexthenforcol=coltomin(col+len-1)(size.cols-2)doLTerm_draw.set_style(LTerm_draw.pointctx0col)reverse_styledone;(* Draw a separator. *)LTerm_draw.draw_piecectx0(col+len)vline;letcol=col+len+1inifcol<size.cols-2thenloop(idx+1)colwordsinloopcomp_start0comp_words;matrixelsebeginlettotal_height=prompt_input_heightinletmatrix_size={cols=size.cols+1;rows=ifdisplayedthenmaxtotal_heightheightelsetotal_height}inletmatrix=LTerm_draw.make_matrixmatrix_sizeinheight<-total_height;draw_styled_with_newlinesmatrixsize.cols00prompt;draw_styled_with_newlinesmatrixsize.colspos_after_prompt.rowpos_after_prompt.colstyled;matrixendinLTerm.hide_cursorterm>>=fun()->beginifdisplayedthen(* Go back to the beginning of displayed text. *)LTerm.moveterm(-cursor.row)(-cursor.col)elsereturn()end>>=fun()->(* Display everything. *)LTerm.print_box_with_newlinestermmatrix>>=fun()->(* Update the cursor. *)cursor<-pos_cursor;(* Move the cursor to the right position. *)LTerm.moveterm(cursor.row-Array.lengthmatrix+1)cursor.col>>=fun()->LTerm.show_cursorterm>>=fun()->LTerm.flushterm>>=fun()->displayed<-true;return()endelsereturn()methoddraw_success=letsize=S.valuesizeinifsize.rows>0&&size.cols>0thenbeginletstyled,_position=self#stylisetrueinletprompt=S.valuepromptin(ifdisplayedthenLTerm.moveterm(-cursor.row)(-cursor.col)>>=fun()->LTerm.clear_screen_nexttermelsereturn())>>=fun()->LTerm.fprintstermprompt>>=fun()->LTerm.fprintlstermstyledendelsereturn()methoddraw_failure=self#draw_successmethodhide=ifvisiblethenbeginvisible<-false;Lwt_mutex.lockdraw_mutex>>=fun()->Lwt.finalize(fun()->letsize=S.valuesizeinifdisplayed&&size.rows>0&&size.cols>0thenletmatrix_size={cols=size.cols+1;rows=height}inletmatrix=LTerm_draw.make_matrixmatrix_sizeinforrow=0toheight-1doLTerm_draw.draw_char_matrixmatrixrow0newline;done;LTerm.moveterm(-cursor.row)(-cursor.col)>>=fun()->LTerm.print_box_with_newlinestermmatrix>>=fun()->LTerm.moveterm(1-Array.lengthmatrix)0>>=fun()->cursor<-{row=0;col=0};height<-0;displayed<-false;return()elsereturn())(fun()->Lwt_mutex.unlockdraw_mutex;return())endelsereturn()methodshow=ifnotvisiblethenbeginvisible<-true;self#queue_draw_updateendelsereturn()valmutablemode=Nonevalmutablelocal_bindings=Bindings.emptymethodbindkeysactions=local_bindings<-Bindings.addkeysactionslocal_bindingsmethodprivatekeyseqkeys=matchkeyswith|[]->return(ContinueLoop[])|key::tl->letres=matchresolverwith|Someres->res|None->Bindings.resolver[Bindings.pack(funx->x)local_bindings;Bindings.pack(funx->x)!bindings;Bindings.pack(List.map(funx->Editx))!LTerm_edit.bindings]inmatchBindings.resolvekeyreswith|Bindings.Acceptedactions->resolver<-None;set_key_sequence[];self#exec~keys:tlactions|Bindings.Continueres->resolver<-Someres;set_key_sequence(S.valuekey_sequence@[key]);return(ContinueLooptl)|Bindings.Rejected->set_key_sequence[];ifresolver=Nonethenmatchkeywith|{control=false;meta=false;shift=false;code=Charch}->Zed_macro.addself#macro(Edit(LTerm_edit.Zed(Zed_edit.Insert(Zed_char.unsafe_of_uCharch))));self#insertch|{code=Charch;_}whenLTerm.windowsterm&&UChar.codech>=32->(* Windows reports Shift+A for A, ... *)Zed_macro.addself#macro(Edit(LTerm_edit.Zed(Zed_edit.Insert(Zed_char.unsafe_of_uCharch))));self#insertch|_->()elseresolver<-None;return(ContinueLooptl)valresult=Lwt_mvar.create_empty()methodprivatelisten_vivi_editexnBox=letmsgBox=vi_edit#action_outputinletrecperform_actions=function|[]->return(ContinueLoop[])|action::tl->LTerm_vi.performvi_editself#contextself#execaction>>=function|Result_asr->returnr|ContinueLoop_->perform_actionstlinletreclisten()=set_key_sequence[];LTerm_vi.Concurrent.MsgBox.getmsgBox>>=(function|Bypasskeyseq->letkeyseq=List.mapLTerm_vi.of_vi_keykeyseqinself#process_keyskeyseq>>=(function|Resultr->Lwt_mvar.putresultr|ContinueLoop_->listen())|Dummy->listen()|Viactions->perform_actionsactions>>=function|ContinueLoop_->listen()|Resultr->Lwt_mvar.putresultr)inletthread=Lwt.catchlisten(funexn->Lwt_mvar.putexnBoxexn)invi_thread<-Some(thread)methodprivateprocess_keyskeys=self#keyseqkeys>>=function|Resultr->return(Resultr)|ContinueLoopkeys->matchkeyswith|[]->return(ContinueLoop[])|_->self#process_keyskeys(* The main loop. *)methodprivateloop=letread_event=matchvi_editwith|Some_->Lwt.pause()>>=fun()->Lwt.(>|=)(LTerm.read_eventterm)(funev->Evev)|None->Lwt.(>|=)(LTerm.read_eventterm)(funev->Evev)inLwt.pick[read_event;Lwt.(>|=)(Lwt_mvar.takeresult)(funr->Loop_resultr);Lwt.(>|=)(Lwt_mvar.takeself#interrupt)(fune->Interruptede);]>>=function|Loop_resultr->returnr|Interruptedexn->raiseexn|Evev->matchevwith|LTerm_event.Resizesize->set_sizesize;self#loop|LTerm_event.Keykey->(matchS.valueeditor_modewith|LTerm_editor.Default->self#process_keys[key]>>=(function|Resultr->returnr|ContinueLoop_->self#loop)|LTerm_editor.Vi->matchvi_editwith|Somevi_edit->set_key_sequence(S.valuekey_sequence@[key]);LTerm_vi.Concurrent.MsgBox.putvi_edit#i(LTerm_vi.of_lterm_keykey)>>=fun()->self#loop|None->self#process_keys[key]>>=(function|Resultr->returnr|ContinueLoop_->self#loop)(* falllback to the default mode *))|_->self#loopmethodcreate_temporary_file_for_external_editor=Filename.temp_file"lambda-term"".txt"methodexternal_editor=trySys.getenv"EDITOR"withNot_found->"vi"methodprivateexec?(keys=[])actions=matchactionswith|Accept::_whenS.valueself#mode=Edition->Zed_macro.addself#macroAccept;return(Resultself#eval)|Clear_screen::actions->Zed_macro.addself#macroClear_screen;LTerm.clear_screenterm>>=fun()->LTerm.gototerm{row=0;col=0}>>=fun()->displayed<-false;self#queue_draw_update>>=fun()->self#exec~keysactions|EditLTerm_edit.Play_macro::actions->Zed_macro.cancelself#macro;self#exec~keys(Zed_macro.contentsmacro@actions)|Suspend::actions->ifSys.win32thenself#exec~keysactionselsebeginletis_visible=visibleinself#hide>>=fun()->LTerm.flushterm>>=fun()->beginmatchmodewith|Somemode->LTerm.leave_raw_modetermmode|None->return()end>>=fun()->Unix.kill(Unix.getpid())Sys.sigtstp;beginmatchLTerm.is_a_ttytermwith|true->LTerm.enter_raw_modeterm>>=funm->mode<-Somem;return()|false->return()end>>=fun()->(ifis_visiblethenself#showelsereturn())>>=fun()->self#exec~keysactionsend|Edit_with_external_editor::actions->beginletis_visible=visibleinself#hide>>=fun()->LTerm.flushterm>>=fun()->beginmatchmodewith|Somemode->LTerm.leave_raw_modetermmode|None->return()end>>=fun()->lettemp_fn=self#create_temporary_file_for_external_editorinletinput=Zed_rope.to_string(Zed_edit.textself#edit)inLwt_io.with_file~mode:Outputtemp_fn(funoc->Lwt_io.write_lineoc(Zed_string.to_utf8input))>>=fun()->leteditor=self#external_editorinPrintf.ksprintfLwt_unix.system"%s %s"editor(Filename.quotetemp_fn)>>=funstatus->(ifstatus<>WEXITED0thenLwt_io.eprintf"`%s %s' exited with status %d\n"editortemp_fn(matchstatuswith|WEXITEDn->n|_->255)elseLwt.try_bind(fun()->Lwt_io.with_file~mode:Inputtemp_fnLwt_io.read)(funs->lets=Zed_utf8.rstripsinZed_edit.goto_botself#context;Zed_edit.replaceself#context(Zed_rope.length(Zed_edit.textself#edit))(Zed_rope.of_string(Zed_string.unsafe_of_utf8s));Lwt.return())(function|Unix.Unix_error(err,_,_)->Lwt_io.eprintf"%s: %s\n"temp_fn(Unix.error_messageerr)|exn->Lwt.failexn))>>=fun()->beginmatchLTerm.is_a_ttytermwith|true->LTerm.enter_raw_modeterm>>=funm->mode<-Somem;return()|false->return()end>>=fun()->(ifis_visiblethenself#showelsereturn())>>=fun()->self#exec~keysactionsend|action::actions->self#send_actionaction;self#exec~keysactions|[]->return(ContinueLoopkeys)methodrun=(* Update the size with the current size. *)set_size(LTerm.sizeterm);running<-true;(* Redraw everything when needed. *)letevent=E.map_p(fun()->ifrunningthenself#queue_draw_updateelsereturn())(E.select[E.stamp(S.changessize)();Zed_edit.updateself#edit[Zed_edit.cursorself#context];E.stamp(S.changesprompt)();E.stamp(S.changesself#completion_words)();E.stamp(S.changesself#completion_index)();E.stamp(S.changesself#completion_start)();E.stamp(S.changesself#message)();])inbeginmatchLTerm.is_a_ttytermwith|true->LTerm.enter_raw_modeterm>>=funm->mode<-Somem;return()|false->return()end>>=fun()->beginLwt.finalize(fun()->Lwt.catch(fun()->(* Go to the beginning of line otherwise all offset
calculation will be false. *)LTerm.fprintterm"\r">>=fun()->self#queue_draw_update>>=fun()->self#loop)(funexn->running<-false;E.stopevent;Lwt_mutex.with_lockdraw_mutex(fun()->self#draw_failure)>>=fun()->Lwt.failexn))(fun()->matchmodewith|Somemode->LTerm.leave_raw_modetermmode|None->return())end>>=funresult->running<-false;E.stopevent;Lwt_mutex.with_lockdraw_mutex(fun()->self#draw_success)>>=fun()->returnresultend