123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294open!Core_kernelopen!ImportmoduleQ=structincludeQlettabulated_list_entries="tabulated-list-entries"|>Symbol.internandtabulated_list_format="tabulated-list-format"|>Symbol.internandtabulated_list_get_id="tabulated-list-get-id"|>Symbol.internandtabulated_list_init_header="tabulated-list-init-header"|>Symbol.internandtabulated_list_mode="tabulated-list-mode"|>Symbol.internandtabulated_list_print="tabulated-list-print"|>Symbol.internandtabulated_list_sort_key="tabulated-list-sort-key"|>Symbol.internendmoduleF=structopen!Funcallopen!Value.Typelettabulated_list_get_id=Q.tabulated_list_get_id<:nullary@->return(optionvalue)andtabulated_list_init_header=Q.tabulated_list_init_header<:nullary@->returnnilandtabulated_list_print=Q.tabulated_list_print<:bool@->bool@->returnnilendmoduleColumn=structmoduleFormat=structtype'at={align_right:bool;header:string;pad_right:int;sortable:bool;width:'a}[@@derivingsexp_of]letcreate?(align_right=false)?(pad_right=1)?(sortable=true)~header~width()={align_right;pad_right;sortable;header;width};;moduleFixed_width=structtypenonrect=intt[@@derivingsexp_of]moduleProperty=structtypet=|Align_rightofbool|Pad_rightofintletto_values=function|Align_rightb->[Q.K.right_align|>Symbol.to_value;b|>Value.of_bool]|Pad_rightpadding->[Q.K.pad_right|>Symbol.to_value;padding|>Value.of_int_exn];;letof_values(keyword,value)=ifValue.equal(Q.K.right_align|>Symbol.to_value)keywordthenAlign_right(Value.to_boolvalue)elseifValue.equal(Q.K.pad_right|>Symbol.to_value)keywordthenPad_right(Value.to_int_exnvalue)elseraise_s[%sexp"Invalid Property keyword",(keyword:Value.t),(value:Value.t)];;endmoduleProperties=structletrecpairs=function|[]->[]|[_]->raise_s[%sexp"Received an odd number of list elements"]|a::b::rest->(a,b)::pairsrest;;lettype_=letopenValue.Typeinmap(listvalue)~name:[%sexp"tabulated-list-column-format-property-list"]~of_:(funx->List.map(pairsx)~f:Property.of_values)~to_:(List.concat_map~f:Property.to_values);;endlettype_=letformat=createinletopenValue.Typeinmap(tuplestring(tupleint(tupleboolProperties.type_)))~name:[%sexp"Column.Format"]~of_:(fun(header,(width,(sortable,props)))->lett=format~header~sortable~width()inList.foldprops~init:t~f:(funt->function|Align_rightalign_right->{twithalign_right}|Pad_rightpad_right->{twithpad_right}))~to_:(fun{align_right;header;pad_right;sortable;width}->header,(width,(sortable,[Align_rightalign_right;Pad_rightpad_right])));;endendmoduleVariable_width=structtypet={max_width:intoption;min_width:int}[@@derivingcompare,fields,sexp_of]letcreate=Fields.createletto_fixed_widthtvalues=letwidth=List.mapvalues~f:String.length|>List.max_elt~compare:[%compare:int]|>Option.value~default:0inOption.foldt.max_width~init:width~f:Int.min|>Int.maxt.min_width;;endtype'recordt={field_of_record:'record->string;format:Variable_width.tFormat.t}[@@derivingfields]letcreate?align_right?max_width?min_width?pad_right?sortable~headerfield_of_record={field_of_record;format=(letmin_width=Option.foldmin_width~init:(String.lengthheader)~f:Int.maxinletwidth=Variable_width.create~max_width~min_widthinFormat.create?align_right?pad_right?sortable~header~width())};;letfixed_width_formattvalues={t.formatwithwidth=List.mapvalues~f:t.field_of_record|>Variable_width.to_fixed_widtht.format.width};;letfirst_line?align_right?max_width?min_width?pad_right?sortable~headerfield_of_record=create?align_right?max_width?min_width?pad_right?sortable~header(funrecord->letstr=field_of_recordrecordin(matchString.split_linesstrwith|line::_::_->sprintf"%s..."(String.rstripline)|[]|[_]->str)|>String.strip);;lettime?align_right?pad_right?sortable~header~zonefield_of_record=create?align_right?pad_right?sortable~header(funrecord->lettime=field_of_recordrecordinletdate,ofday=Time.to_date_ofday~zonetimeinconcat~sep:" "[Date.to_stringdate;Time.Ofday.to_sec_stringofday]);;endtype('record,'id)t={columns:'recordColumn.tlist;entries_var:'recordlistBuffer_local.t;id_equal:'id->'id->bool;id_of_record:'record->'id;id_type:'idValue.Type.t;major_mode:Major_mode.t}[@@derivingfields]letkeymapt=Major_mode.keymap(major_modet)lettabulated_list_format_var=Buffer_local.wrap_existingQ.tabulated_list_format(Value.Type.vectorColumn.Format.Fixed_width.type_);;lettabulated_list_sort_key_var=Buffer_local.wrap_existingQ.tabulated_list_sort_keyValue.Type.(option(tuplestringbool));;letdraw?sort_bytrows=(* Work around an emacs bug where tabulated-list.el doesn't check that we're sorting by
a sortable column *)Option.itersort_by~f:(fun(sort_header,_)->matchList.findt.columns~f:(funcolumn->String.equalsort_headercolumn.format.header)with|None->raise_s[%sexp"Unknown header to sort by",(sort_header:string)]|Somecolumn->ifnotcolumn.format.sortablethenraise_s[%sexp"Column is not sortable",(sort_header:string)]);Current_buffer.set_buffer_localtabulated_list_sort_key_var(Option.mapsort_by~f:(Tuple2.map_snd~f:(function|`Ascending->false|`Descending->true)));Current_buffer.set_buffer_localt.entries_varrows;Current_buffer.set_buffer_localtabulated_list_format_var(Array.of_list(List.mapt.columns~f:(funcolumn->Column.fixed_width_formatcolumnrows)));F.tabulated_list_init_header();F.tabulated_list_printtruefalse;;moduleTabulated_list_mode=(valMajor_mode.wrap_existing[%here]Q.tabulated_list_mode)letcreatemajor_modecolumns~id_equal~id_type~id_of_record=ifnot(Major_mode.is_derivedmajor_mode~from:Tabulated_list_mode.major_mode)thenraise_s[%sexp"[Tabulated_list.create] called on a major mode not derived from \
[Tabulated_list.Tabulated_list_mode.major_mode]."];letentries_var=letentry_type=letopenValue.Typeinmap(tupleid_type(tuple(vectorstring)unit))~name:[%message"tabulated-list-entries"(id_type:_Value.Type.t)]~of_:(fun_->raise_s[%sexp"reading tabulated-list-entries is not supported"])~to_:(funrecord->(id_of_recordrecord,(columns|>List.map~f:(fun(column:_Column.t)->column.field_of_recordrecord)|>Array.of_list,())))inBuffer_local.wrap_existingQ.tabulated_list_entries(Value.Type.listentry_type)in{columns;entries_var;id_equal;id_of_record;id_type;major_mode};;letget_id_at_point_exnt=Option.map(F.tabulated_list_get_id())~f:t.id_type.of_value_exn;;letmove_point_to_idtid=Point.goto_min();letrecloop()=matchget_id_at_point_exntwith|None->(* This only happens after we go past the last row. *)raise_s[%sexp"Could not find row with given id"]|Someid_at_point->(matcht.id_equalidid_at_pointwith|false->Point.forward_line1;loop()|true->())inloop();;letcurrent_buffer_has_entries()=not(Value.is_nil(Current_buffer.get_buffer_local(Buffer_local.wrap_existingQ.tabulated_list_entriesValue.Type.value)));;letrevert_hook=Hook.create("tabulated-list-revert-hook"|>Symbol.intern)~hook_type:Normal;;