123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486(**************************************************************************)(* *)(* This file is part of Frama-C. *)(* *)(* Copyright (C) 2007-2025 *)(* CEA (Commissariat à l'énergie atomique et aux énergies *)(* alternatives) *)(* *)(* you can redistribute it and/or modify it under the terms of the GNU *)(* Lesser General Public License as published by the Free Software *)(* Foundation, version 2.1. *)(* *)(* It is distributed in the hope that it will be useful, *)(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)(* GNU Lesser General Public License for more details. *)(* *)(* See the GNU Lesser General Public License version 2.1 *)(* for more details (enclosed in the file licenses/LGPLv2.1). *)(* *)(**************************************************************************)moduleRangemap=Rgmap(* -------------------------------------------------------------------------- *)(* --- Text with Tagging Formatter --- *)(* -------------------------------------------------------------------------- *)typetag=|TAGofGText.tag|MARKofint*string|LINKofint*string|PLAINletrecfilter_tagstgs=function|[]->tgs|TAGt::style->filter_tags(t::tgs)style|(MARK_|LINK_|PLAIN)::style->filter_tagstgsstyleletsplit_tagtag=letreclookuptagkn=ifk<ntheniftag.[k]=':'thenString.subtag0k,String.subtag(k+1)(n-k-1)elselookuptag(succk)nelsetag,""inlookuptag0(String.lengthtag)letcss_sheet=["ul",[`UNDERLINE`SINGLE];"st",[`STRIKETHROUGHtrue];"bf",[`WEIGHT`BOLD];"it",[`STYLE`ITALIC];"red",[`FOREGROUND"red"];"blue",[`FOREGROUND"blue"];"green",[`FOREGROUND"darkgreen"];"orange",[`FOREGROUND"orange"];"hover",[`BACKGROUND"lightblue"];"link",[`FOREGROUND"blue"];]type'aentry=int*int*'aletrecfiree=function[]->()|f::fs->fe;fireefsclasstype['a]marker=objectmethodset_style:GText.tag_propertylist->unitmethodset_hover:GText.tag_propertylist->unitmethodconnect:(GdkEvent.Button.t->'aentry->unit)->unitmethodon_click:('aentry->unit)->unitmethodon_double_click:('aentry->unit)->unitmethodon_right_click:('aentry->unit)->unitmethodon_shift_click:('aentry->unit)->unitmethodon_add:('aentry->unit)->unitmethodwrap:(Format.formatter->'a->unit)->Format.formatter->'a->unitmethodmark:'b.'a->(Format.formatter->'b->unit)->Format.formatter->'b->unitmethodadd:'aentry->unitendtypestyle=NoStyle|StyleSet|StyleofGText.tag_propertylistletconfiguretag=function|NoStyle->NoStyle|StyleSet->StyleSet|Style[]->NoStyle|Stylesty->tag#set_propertiessty;StyleSet(* -------------------------------------------------------------------------- *)(* --- Monomorphic Marker --- *)(* -------------------------------------------------------------------------- *)typeblind={hover:GText.tag;click:(bool->GdkEvent.Button.t->unit);}typeregistry=blindentry->unittypewrapper=(int->int->unit)->(Format.formatter->unit)->Format.formatter->unitclass['a]poly_marker(buffer:GText.buffer)(registry:registry)(wrapper:wrapper)=letstyle=buffer#create_tag[]inlethover=buffer#create_tag[]inobject(self)(*--- Style Configuration ---*)valmutablestyle_props=NoStylevalmutablehover_props=Style(List.assoc"hover"css_sheet)valmutableto_configure=truemethodset_stylesty=assertto_configure;style_props<-Stylestymethodset_hoversty=assertto_configure;hover_props<-Stylestymethodprivateconfigure=ifto_configurethenbeginstyle_props<-configurestylestyle_props;hover_props<-configurehoverhover_props;to_configure<-false;endvalmutabledemon:(GdkEvent.Button.t->'aentry->unit)list=[]valmutabledemon_click:('aentry->unit)list=[]valmutabledemon_double:('aentry->unit)list=[]valmutabledemon_right:('aentry->unit)list=[]valmutabledemon_shift:('aentry->unit)list=[]valmutabledemon_added:('aentry->unit)list=[](*--- Signal Connection ---*)methodconnectf=demon<-demon@[f]methodon_clickd=demon_click<-demon_click@[d]methodon_double_clickd=demon_double<-demon_double@[d]methodon_right_clickd=demon_right<-demon_right@[d]methodon_shift_clickd=demon_shift<-demon_shift@[d]methodon_addd=demon_added<-demon_added@[d](*--- Adding ---*)methodadd(e:'aentry)=beginself#configure;let(p,q,_)=einifstyle_props=StyleSetthenbeginletstart=buffer#get_iter(`OFFSETp)inletstop=buffer#get_iter(`OFFSETq)inbuffer#apply_tagstyle~start~stopend;letclickdoubleevt=List.iter(funf->fevte)demon;ifdoublethenfireedemon_doubleelseletstate=GdkEvent.Button.stateevtinifGdk.Convert.test_modifier`BUTTON3statethenfireedemon_rightelseifGdk.Convert.test_modifier`BUTTON1statethenifGdk.Convert.test_modifier`SHIFTstatethenfireedemon_shiftelsefireedemon_clickinregistry(p,q,{hover;click});ignore(fireedemon_added);endmethodwrappp(fmt:Format.formatter)(w:'a):unit=self#markwppfmtwmethodmark:'b.'a->(Format.formatter->'b->unit)->Format.formatter->'b->unit=funeppfmtw->wrapper(funpq->self#add(p,q,e))(funfmt->ppfmtw)fmtend(* -------------------------------------------------------------------------- *)(* --- Text Widget --- *)(* -------------------------------------------------------------------------- *)classtext?(autoscroll=false)?(width=80)?(indent=60)()=letbuffer=GText.buffer()inletreact=buffer#create_tag[]inletview=GText.view~buffer~editable:false~cursor_visible:false~justification:`LEFT~wrap_mode:`NONE~accepts_tab:false~show:true()inletscroll=GBin.scrolled_window()inobject(self)valtext=Buffer.create80valcss=Hashtbl.create32valmarks:(string,int->int->unit)Hashtbl.t=Hashtbl.create32valmutablelinks:stringmarkeroption=Nonevalmutablewidth=widthvalmutablehrule=""valmutableruled=falsevalmutableindent=indentvalmutablehid=0valmutableautoscroll=autoscrollvalmutablestyle=[]valmutablefmtref=Nonevalmutablereactive=falsevalmutableindex:blindRangemap.t=Rangemap.emptyvalmutablehovered=Nonevalmutabledouble=false(* -------------------------------------------------------------------------- *)(* --- Text Initializer --- *)(* -------------------------------------------------------------------------- *)initializerbegin(* Ignore default pango contextual menu (copy/cut/paste etc...), as this
widget is read-only *)ignore(view#event#connect#button_press~callback:(funev->GdkEvent.Button.buttonev=3));scroll#addview#coerceend(* -------------------------------------------------------------------------- *)(* --- Text Formatter --- *)(* -------------------------------------------------------------------------- *)methodprivateflush()=ifBuffer.lengthtext>0thenbeginlets=Wutil.to_utf8(Buffer.contentstext)inBuffer.cleartext;lettags=filter_tags[]styleinletiter=buffer#end_iterinbuffer#insert~tags~iters;ifreactivethenletstart,stop=buffer#boundsinbuffer#apply_tagreact~start~stop;endmethodprivateopen_tagname=letname=Extlib.format_string_of_stagnameinself#flush();style<-self#tagname::style;""methodprivateclose_tag_name=self#flush();matchstylewith|[]->""|MARK(p,mrk)::sty->style<-sty;self#markpmrk;""|LINK(p,lnk)::sty->style<-sty;self#linkplnk;""|(TAG_|PLAIN)::sty->style<-sty;""methodfmt=matchfmtrefwithSomefmt->fmt|None->letopenFormatinletoutput_stringsab=ifb>0thenBuffer.add_substringtextsabinletfmt=Format.make_formatteroutput_stringself#flushinlettagger=pp_get_formatter_stag_functionsfmt()inpp_set_formatter_stag_functionsfmt{taggerwithmark_open_stag=self#open_tag;mark_close_stag=self#close_tag;};Format.pp_set_print_tagsfmtfalse;Format.pp_set_mark_tagsfmttrue;Format.pp_set_marginfmtwidth;Format.pp_set_max_indentfmtindent;fmtref<-Somefmt;fmtmethodoffset=self#flush();buffer#end_iter#offsetmethodset_widthw=width<-w;hrule<-"";matchfmtrefwithNone->()|Somefmt->Format.pp_set_marginfmtwmethodset_indentp=indent<-p;matchfmtrefwithNone->()|Somefmt->Format.pp_set_max_indentfmtp(* -------------------------------------------------------------------------- *)(* --- Link & Marking --- *)(* -------------------------------------------------------------------------- *)methodlinks=matchlinkswith|Somemarker->marker|None->letmarker=self#markerinmarker#set_style(List.assoc"link"css_sheet);marker#set_hover(List.assoc"hover"css_sheet);links<-Somemarker;markermethodprivatelinkpname=letq=buffer#end_iter#offsetinself#links#add(p,q,name)methodprivatemarkpname=letq=buffer#end_iter#offsetinList.iter(funf->fpq)(Hashtbl.find_allmarksname)methodon_linkf=self#links#on_click(fun(_,_,lnk)->flnk)methodwrapfppfmt=beginletsid=hid<-succhid;Printf.sprintf">%X"hidinHashtbl.addmarkssid(funpq->Hashtbl.removemarkssid;fpq);Format.pp_open_stagfmt(Format.String_tagsid);let()=ppfmtinFormat.pp_close_stagfmt();end(* -------------------------------------------------------------------------- *)(* --- Tag Marking --- *)(* -------------------------------------------------------------------------- *)methodprivatecss_stylenameprops=letsty=TAG(buffer#create_tag~nameprops)inHashtbl.replacecssnamesty;stymethodprivatetagname=ifHashtbl.memmarksnamethenMARK(buffer#end_iter#offset,name)elsetryHashtbl.findcssnamewithNot_found->tryself#css_stylename(List.assocnamecss_sheet)withNot_found->beginmatchsplit_tagnamewith|"fg",color->self#css_stylename[`FOREGROUNDcolor]|"bg",color->self#css_stylename[`BACKGROUNDcolor]|"link",name->LINK(buffer#end_iter#offset,name)|_->PLAINendmethodset_csssheet=List.iter(fun(name,tags)->ignore(self#css_stylenametags))sheetmethodset_stylenamepq=matchself#tagnamewith|PLAIN|LINK_|MARK_->()|TAGtag->letstart=buffer#get_iter(`OFFSETp)inletstop=buffer#get_iter(`OFFSETq)inbuffer#apply_tagtag~start~stopmethodremove_stylenamepq=matchHashtbl.findcssnamewith|PLAIN|LINK_|MARK_->()|TAGtag->letstart=buffer#get_iter(`OFFSETp)inletstop=buffer#get_iter(`OFFSETq)inbuffer#remove_tagtag~start~stopmethodremove_allnames=letstart,stop=buffer#boundsinList.iter(funname->matchHashtbl.findcssnamewith|TAGtag->buffer#remove_tagtag~start~stop|PLAIN|LINK_|MARK_->())names(* -------------------------------------------------------------------------- *)(* --- Hover & Mark Dispatcher --- *)(* -------------------------------------------------------------------------- *)methodprivateset_reactive=ifnotreactivethenletcallback~origin:_evtiter=(* return false to propagate events *)matchGdkEvent.get_typeevtwith|`BUTTON_PRESS->double<-false;false|`TWO_BUTTON_PRESS->double<-true;false|`BUTTON_RELEASE->beginmatchhoveredwith|None->()|Some(_,_,blind)->blind.clickdouble(GdkEvent.Button.castevt)end;false|`MOTION_NOTIFY->letoffset=GtkText.Iter.get_offsetiterinletentry=trySome(Rangemap.findoffsetoffsetindex)withNot_found->Noneinself#hoverentry;false|_->falsein(ignore(react#connect#event~callback);reactive<-true)methodprivatehoverh=matchhovered,hwith|Somee0,Someewhene==e0->()|None,None->()|_->begin(matchhoveredwithNone->()|Some(_,_,{hover})->letstart,stop=buffer#boundsinbuffer#remove_taghover~start~stop);(matchhwithNone->()|Some(a,b,{hover})->letstart=buffer#get_iter(`OFFSETa)inletstop=buffer#get_iter(`OFFSETb)inself#hoverNone;buffer#apply_taghover~start~stop);hovered<-hendmethodprivateregistere=index<-Rangemap.addeindex(* -------------------------------------------------------------------------- *)(* --- User API --- *)(* -------------------------------------------------------------------------- *)methodset_autoscrolls=autoscroll<-smethodprintf:'a.?scroll:bool->('a,Format.formatter,unit)format->'a=fun?(scroll=autoscroll)text->(* Save current number of lines in the buffer *)letline=view#buffer#line_countinletfinallyfmt=Format.pp_print_flushfmt();Hashtbl.clearmarks;hid<-0;ruled<-false;ifscrollthen(* scrolling must be performed asynchronously using Gtk_helper.later,
otherwise it will not take into account the newly added text. *)Wutil.later(self#scroll~line)inFormat.kfprintffinallyself#fmttextmethodhrule=ifnotruledthenbeginifString.lengthhrule=0thenhrule<-String.makewidth'-';Format.pp_print_stringself#fmthrule;Format.pp_print_newlineself#fmt();ruled<-true;endmethodlines=view#buffer#line_countmethodscroll?line()=letbuf=view#bufferinletline=matchlinewithSomel->l|None->buf#line_countinletiter=buf#get_iter_at_char~line0inignore(view#scroll_to_iter~use_align:true~yalign:0.0iter)methodselect?(scroll=false)(p:int)(q:int)=letbuffer=view#bufferinletstart=buffer#get_iter(`OFFSETp)inletstop=buffer#get_iter(`OFFSETq)inbuffer#select_rangestartstop;ignore(view#scroll_to_iter~use_align:scroll~yalign:0.3start)methodclear=beginFormat.pp_print_flushself#fmt();Hashtbl.clearmarks;hid<-0;buffer#delete~start:buffer#start_iter~stop:buffer#end_iter;index<-Rangemap.empty;self#hoverNone;endmethodcoerce=scroll#coercemethodwidget=(self:>Widget.t)methodset_enabled=Wutil.set_enabledscrollmethodset_visible=Wutil.set_visiblescrollmethodset_font=Wutil.set_fontviewmethodset_monospace=Wutil.set_monospaceviewmethodmarker:'a.'amarker=leth=newpoly_markerbufferself#registerself#wrapinself#set_reactive;(h:>_marker)methodget_view=viewend