35
36:- module(prolog_help,
37 [ help/0,
38 help/1, 39 apropos/1, 40 apropos/2, 41 help_apropos/4,
42 help_text/2 43 ]). 44:- use_module(library(pldoc), []). 45:- use_module(library(isub), [isub/4]). 46:- autoload(library(apply), [maplist/3]). 47:- autoload(library(error), [must_be/2]). 48:- autoload(library(lists), [append/3, sum_list/2, select/3]). 49:- autoload(library(option), [option/3]). 50:- autoload(library(pairs), [pairs_values/2]). 51:- autoload(library(porter_stem), [tokenize_atom/2]). 52:- autoload(library(process),
53 [process_create/3, process_which/2, process_wait/2]). 54:- autoload(library(sgml), [load_html/3]). 55:- autoload(library(solution_sequences), [distinct/1]). 56:- autoload(library(http/html_write), [html/3, print_html/1]). 57:- autoload(library(lynx/html_text), [html_text/2]). 58:- autoload(pldoc(doc_man),
59 [ man_page/4, pldoc_href_object/2,
60 man_object_uri/2, man_uri_object/2,
61 xpce_object_label/2
62 ]). 63:- autoload(library(pce), [send/3, get/3]). 64:- autoload(pldoc(doc_modes), [(mode)/2]). 65:- autoload(pldoc(doc_words), [doc_related_word/3]). 66:- autoload(pldoc(man_index), [man_object_property/2, doc_object_identifier/2]). 67:- autoload(library(prolog_code), [pi_head/2]). 68:- autoload(library(prolog_xref), [xref_source/2]). 69:- use_module(library(lynx/pldoc_style), []). 70:- autoload(library(terms), [mapsubterms/3]). 71
96
97:- meta_predicate
98 with_pager(0). 99
100:- multifile
101 show_html_hook/1. 102
105:- create_prolog_flag(help_pager, default,
106 [ type(term),
107 keep(true)
108 ]). 109
161
162help :-
163 notrace(show_matches([help/1, apropos/1], exact-help)).
164
165help(What) :-
166 notrace(help_no_trace(What)).
167
168help_no_trace(What) :-
169 help_objects_how(What, Matches, How),
170 !,
171 show_matches(Matches, How-What).
172help_no_trace(What) :-
173 print_message(warning, help(not_found(What))).
174
175show_matches(Matches, HowWhat) :-
176 help_html(Matches, HowWhat, HTML),
177 !,
178 show_html(HTML).
179
185
186show_html(HTML) :-
187 show_html_hook(HTML),
188 !.
189show_html(HTML) :-
190 load_html(string(HTML), DOM0, []),
191 mapsubterms(man_link, DOM0, DOM),
192 page_width(PageWidth),
193 LineWidth is PageWidth - 4,
194 with_pager(html_text(DOM, [width(LineWidth)])).
195
196help_html(Matches, How, HTML) :-
197 ( current_prolog_flag(epilog, true)
198 -> Extra = [link_scheme(man)]
199 ; Extra = []
200 ),
201 phrase(html(html([ head([]),
202 body([ \match_type(How),
203 dl(\man_pages(Matches,
204 [ no_manual(fail),
205 links(false),
206 link_source(false),
207 navtree(false),
208 server(false),
209 qualified(always)
210 | Extra
211 ]))
212 ])
213 ])),
214 Tokens),
215 !,
216 with_output_to(string(HTML),
217 print_html(Tokens)).
218
219match_type(exact-_) -->
220 [].
221match_type(dwim-For) -->
222 html(p(class(warning),
223 [ 'WARNING: No matches for "', span(class('help-query'), For),
224 '" Showing closely related results'
225 ])).
226
227man_pages([], _) -->
228 [].
229man_pages([H|T], Options) -->
230 ( man_page(H, Options)
231 -> []
232 ; html(p(class(warning),
233 [ 'WARNING: No help for ~p'-[H]
234 ]))
235 ),
236 man_pages(T, Options).
237
238page_width(Width) :-
239 tty_width(W),
240 Width is min(100,max(50,W)).
241
246
247tty_width(W) :-
248 \+ running_under_emacs,
249 catch(tty_size(_, W), _, fail),
250 !.
251tty_width(80).
252
253help_objects_how(Spec, Objects, exact) :-
254 help_objects(Spec, exact, Objects),
255 !.
256help_objects_how(Spec, Objects, dwim) :-
257 help_objects(Spec, dwim, Objects),
258 !.
259
260help_objects(Spec, How, Objects) :-
261 findall(ID-Obj, help_object(Spec, How, Obj, ID), Objects0),
262 Objects0 \== [],
263 sort(1, @>, Objects0, Objects1),
264 pairs_values(Objects1, Objects2),
265 sort(Objects2, Objects).
266
267help_object(Fuzzy/Arity, How, Name/Arity, ID) :-
268 match_name(How, Fuzzy, Name),
269 man_object_property(Name/Arity, id(ID)).
270help_object(Fuzzy//Arity, How, Name//Arity, ID) :-
271 match_name(How, Fuzzy, Name),
272 man_object_property(Name//Arity, id(ID)).
273help_object(Fuzzy/Arity, How, f(Name/Arity), ID) :-
274 match_name(How, Fuzzy, Name),
275 man_object_property(f(Name/Arity), id(ID)).
276help_object(Fuzzy, How, Name/Arity, ID) :-
277 atom(Fuzzy),
278 match_name(How, Fuzzy, Name),
279 man_object_property(Name/Arity, id(ID)).
280help_object(Fuzzy, How, Name//Arity, ID) :-
281 atom(Fuzzy),
282 match_name(How, Fuzzy, Name),
283 man_object_property(Name//Arity, id(ID)).
284help_object(Fuzzy, How, f(Name/Arity), ID) :-
285 atom(Fuzzy),
286 match_name(How, Fuzzy, Name),
287 man_object_property(f(Name/Arity), id(ID)).
288help_object(Fuzzy, How, c(Name), ID) :-
289 atom(Fuzzy),
290 match_name(How, Fuzzy, Name),
291 man_object_property(c(Name), id(ID)).
292help_object(SecID, _How, section(Label), ID) :-
293 atom(SecID),
294 ( atom_concat('sec:', SecID, Label)
295 ; sub_atom(SecID, _, _, 0, '.html'),
296 Label = SecID
297 ),
298 man_object_property(section(_Level,_Num,Label,_File), id(ID)).
299help_object(Func, How, c(Name), ID) :-
300 compound(Func),
301 compound_name_arity(Func, Fuzzy, 0),
302 match_name(How, Fuzzy, Name),
303 man_object_property(c(Name), id(ID)).
305help_object(Obj, _How, Obj, ID) :-
306 man_object_id(Obj, ID).
308help_object(Module, _How, Module:Name/Arity, _ID) :-
309 atom(Module),
310 current_module(Module),
311 atom_concat('sec:', Module, SecLabel),
312 \+ man_object_property(section(_,_,SecLabel,_), _), 313 current_predicate_help(Module:Name/Arity).
314help_object(Module:Name, _How, Module:Name/Arity, _ID) :-
315 atom(Name),
316 current_predicate_help(Module:Name/Arity).
317help_object(Module:Name/Arity, _How, Module:Name/Arity, _ID) :-
318 atom(Name),
319 current_predicate_help(Module:Name/Arity).
320help_object(Name/Arity, _How, Module:Name/Arity, _ID) :-
321 atom(Name),
322 current_predicate_help(Module:Name/Arity).
323help_object(Fuzzy, How, Module:Name/Arity, _ID) :-
324 atom(Fuzzy),
325 match_name(How, Fuzzy, Name),
326 current_predicate_help(Module:Name/Arity).
327
333
334man_object_id(Module:Name/Arity, ID) :-
335 atom(Module),
336 atom(Name),
337 integer(Arity),
338 man_object_property(Module:Name/Arity, id(ID)).
339man_object_id(Module:Name//Arity, ID) :-
340 atom(Module),
341 atom(Name),
342 integer(Arity),
343 man_object_property(Module:Name//Arity, id(ID)).
344man_object_id(section(Label), ID) :-
345 atom(Label),
346 man_object_property(section(_Level,_Num,Label,_File), id(ID)).
347man_object_id(f(Name/Arity), ID) :-
348 atom(Name),
349 integer(Arity),
350 man_object_property(f(Name/Arity), id(ID)).
351man_object_id(c(Name), ID) :-
352 atom(Name),
353 man_object_property(c(Name), id(ID)).
354man_object_id(xpce(Class,Kind,Name), ID) :-
355 atom(Class),
356 atom(Kind),
357 atom(Name),
358 man_object_property(xpce(Class,Kind,Name), id(ID)).
359
366
367current_predicate_help(M:Name/Arity) :-
368 current_predicate(M:Name/Arity),
369 pi_head(Name/Arity,Head),
370 \+ predicate_property(M:Head, imported_from(_)),
371 module_property(M, class(user)),
372 ( mode(M:_, _) 373 -> true
374 ; \+ module_property(M, class(system)),
375 main_source_file(M:Head, File),
376 xref_source(File,[comments(store)])
377 ),
378 mode(M:Head, _). 379
380match_name(exact, Name, Name).
381match_name(dwim, Name, Fuzzy) :-
382 freeze(Fuzzy, dwim_match(Fuzzy, Name)).
383
387
388main_source_file(Pred, File) :-
389 predicate_property(Pred, file(File0)),
390 main_source(File0, File).
391
392main_source(File, Main) :-
393 source_file(File),
394 !,
395 Main = File.
396main_source(File, Main) :-
397 source_file_property(File, included_in(Parent, _Time)),
398 main_source(Parent, Main).
399
400
407
(Goal) :-
409 pager_ok(Pager, Options),
410 !,
411 current_output(Screen),
412 setup_call_cleanup(
413 pager_screen(Screen, enter),
414 paged(Pager, Goal, Options),
415 pager_screen(Screen, leave)).
416with_pager(Goal) :-
417 call(Goal).
418
424
425:- dynamic
426 pager/2. 427
428paged(Pager, Goal, Options) :-
429 Catch = error(io_error(_,_), _),
430 current_output(OldIn),
431 thread_self(Me),
432 setup_call_cleanup(
433 ( process_create(Pager, Options,
434 [stdin(pipe(In)), process(PID)]),
435 assertz(pager(Me, PID), Ref)
436 ),
437 ( set_stream(In, tty(true)),
438 set_output(In),
439 catch(Goal, Catch, true)
440 ),
441 call_cleanup(( set_output(OldIn),
442 close(In, [force(true)]),
443 process_wait(PID, _Status)
444 ),
445 erase(Ref))).
446
459
(_Screen, _Which) :-
461 \+ current_prolog_flag(windows, true),
462 !.
463pager_screen(Screen, _Which) :-
464 \+ stream_property(Screen, tty(true)),
465 !.
466pager_screen(Screen, enter) :-
467 !,
468 format(Screen, '\e[?1049h', []),
469 flush_output(Screen).
470pager_screen(Screen, leave) :-
471 format(Screen, '\e[?1049l', []),
472 flush_output(Screen).
473
(_Path, _Options) :-
475 current_prolog_flag(help_pager, false),
476 !,
477 fail.
478pager_ok(Path, Options) :-
479 current_prolog_flag(help_pager, default),
480 !,
481 stream_property(current_output, tty(true)),
482 \+ running_under_emacs,
483 ( distinct(( getenv('PAGER', Pager)
484 ; Pager = less
485 )),
486 absolute_file_name(path(Pager), Path,
487 [ access(execute),
488 file_errors(fail)
489 ])
490 -> pager_options(Path, Options)
491 ).
492pager_ok(Path, Options) :-
493 current_prolog_flag(help_pager, Term),
494 callable(Term),
495 compound_name_arguments(Term, Pager, Options),
496 ( is_absolute_file_name(Pager)
497 -> Prog = Pager
498 ; Prog = path(Pager)
499 ),
500 process_which(Prog, Path).
501
(Path, Options) :-
503 file_base_name(Path, File),
504 file_name_extension(Base, _, File),
505 downcase_atom(Base, Id),
506 pager_default_options(Id, Options),
507 !.
508pager_options(_, []).
509
(less, ['-r']).
511
512
517
518running_under_emacs :-
519 current_prolog_flag(emacs_inferior_process, true),
520 !.
521running_under_emacs :-
522 getenv('TERM', dumb),
523 !.
524running_under_emacs :-
525 current_prolog_flag(toplevel_prompt, P),
526 sub_atom(P, _, _, _, 'ediprolog'),
527 !.
528
563
564apropos(Query) :-
565 apropos(Query, []).
566
567apropos(Query, Options) :-
568 notrace(apropos_no_trace(Query, Options)).
569
570apropos_no_trace(Query, Options) :-
571 option(limit(Limit), Options, 20),
572 option(offset(From), Options, 0),
573 must_be(positive_integer, Limit),
574 must_be(nonneg, From),
575 findall(Q-(Obj-Summary), help_apropos(Query, Obj, Summary, Q), Pairs),
576 ( Pairs == []
577 -> print_message(warning, help(no_apropos_match(Query)))
578 ; sort(1, >=, Pairs, Sorted),
579 length(Sorted, Total),
580 page(Sorted, From, Limit, Page),
581 pairs_values(Page, Matches),
582 print_message(information,
583 help(apropos_matches(Query, Matches, From, Total)))
584 ).
585
590
591page(List, From, Limit, Page) :-
592 length(List, Len),
593 Skip is min(From, Len),
594 length(Prefix, Skip),
595 append(Prefix, Rest, List),
596 length(Rest, RestLen),
597 Take is min(Limit, RestLen),
598 length(Page, Take),
599 append(Page, _, Rest).
600
606
607help_apropos(Query, Obj, Summary, Q) :-
608 parse_query(Query, Type, Words),
609 man_object_property(Obj, summary(Summary)),
610 apropos_match(Type, Words, Obj, Summary, Q).
611
612parse_query(Type:String, Type, Words) :-
613 !,
614 must_be(atom, Type),
615 must_be(text, String),
616 tokenize_atom(String, Words).
617parse_query(String, _Type, Words) :-
618 must_be(text, String),
619 tokenize_atom(String, Words).
620
621apropos_match(Type, Query, Object, Summary, Q) :-
622 maplist(amatch(Object, Summary), Query, Scores),
623 match_object_type(Type, Object),
624 sum_list(Scores, Q).
625
626amatch(Object, Summary, Query, Score) :-
627 ( doc_object_identifier(Object, String)
628 ; String = Summary
629 ),
630 amatch(Query, String, Score),
631 !.
632
633amatch(Query, To, Quality) :-
634 doc_related_word(Query, Related, Distance),
635 sub_atom_icasechk(To, _, Related),
636 isub(Related, To, false, Quality0),
637 Quality is Quality0*Distance.
638
639match_object_type(Type, _Object) :-
640 var(Type),
641 !.
642match_object_type(Type, Object) :-
643 downcase_atom(Type, LType),
644 object_class(Object, Class),
645 match_object_class(LType, Class).
646
647match_object_class(Type, Class) :-
648 ( TheClass = Class
649 ; class_alias(Class, TheClass)
650 ),
651 sub_atom(TheClass, 0, _, _, Type),
652 !.
653
654class_alias(section, chapter).
655class_alias(function, arithmetic).
656class_alias(cfunction, c_function).
657class_alias(iso_predicate, predicate).
658class_alias(swi_builtin_predicate, predicate).
659class_alias(library_predicate, predicate).
660class_alias(dcg, predicate).
661class_alias(dcg, nonterminal).
662class_alias(dcg, non_terminal).
663
664class_tag(section, 'SEC').
665class_tag(function, 'F').
666class_tag(cfunction, 'C').
667class_tag(iso_predicate, 'ISO').
668class_tag(swi_builtin_predicate, 'SWI').
669class_tag(library_predicate, 'LIB').
670class_tag(dcg, 'DCG').
671class_tag(xpce, 'XPCE').
672
673object_class(section(_Level, _Num, _Label, _File), section).
674object_class(c(_Name), cfunction).
675object_class(f(_Name/_Arity), function).
676object_class(xpce(_Class, _Kind, _Name), xpce).
677object_class(Name/Arity, Type) :-
678 functor(Term, Name, Arity),
679 ( current_predicate(system:Name/Arity),
680 predicate_property(system:Term, built_in)
681 -> ( predicate_property(system:Term, iso)
682 -> Type = iso_predicate
683 ; Type = swi_builtin_predicate
684 )
685 ; Type = library_predicate
686 ).
687object_class(_M:_Name/_Arity, library_predicate).
688object_class(_Name//_Arity, dcg).
689object_class(_M:_Name//_Arity, dcg).
690
696
697help_text(Pred, HelpText) :-
698 help_objects(Pred, exact, Matches), !,
699 catch(help_html(Matches, exact-exact, HtmlDoc), _, fail),
700 setup_call_cleanup(open_string(HtmlDoc, In),
701 load_html(stream(In), Dom, []),
702 close(In)),
703 with_output_to(string(HelpText), html_text(Dom, [])).
704
705
706 709
717
718man_link(element(a, Attrs0, Content), Element) :-
719 select(href=HREF0, Attrs0, Attrs1),
720 \+ sub_atom(HREF0, 0, _, _, 'man:'),
721 ( current_prolog_flag(epilog, true),
722 pldoc_href_object(HREF0, Object),
723 man_object_uri(Object, HREF)
724 -> Element = element(a, [href=HREF|Attrs1], Content)
725 ; Element = element(b, Attrs1, Content)
726 ).
727
734
735apropos_uri(Query, Offset, URI) :-
736 format(atom(URI), 'apropos:~q', [Query+Offset]).
737
738apropos_uri_goal(URI, apropos(Query, [offset(Offset)])) :-
739 atom_concat('apropos:', Text, URI),
740 catch(term_to_atom(Query+Offset, Text), error(_,_), fail),
741 integer(Offset).
742
749
750:- multifile epilog:tty_link_hook/2. 751
752epilog:tty_link_hook(Terminal, URL) :-
753 link_goal(URL, Goal),
754 !, 755 quit_pager(Terminal), 756 ignore(send(Terminal, inject, Goal)).
757
758link_goal(URL, help(Object)) :-
759 man_uri_object(URL, Object).
760link_goal(URL, Goal) :-
761 apropos_uri_goal(URL, Goal).
762
767
(Terminal) :-
769 get(Terminal, thread, Thread),
770 pager(Thread, _PID),
771 !,
772 send(Terminal, send, "q").
773quit_pager(_).
774
775 778
779:- multifile prolog:message//1. 780
781prolog:message(help(not_found(What))) -->
782 [ 'No help for ~p.'-[What], nl,
783 'Use ?- apropos(query). to search for candidates.'-[]
784 ].
785prolog:message(help(no_apropos_match(Query))) -->
786 [ 'No matches for ~p'-[Query] ].
787prolog:message(help(apropos_matches(Query, Pairs, From, Total))) -->
788 { tty_width(W),
789 Width is max(30,W),
790 length(Pairs, Count),
791 End is From+Count
792 },
793 matches(Pairs, Width),
794 ( {End =:= Total, From =:= 0}
795 -> []
796 ; [nl],
797 showing(Query, From, End, Total),
798 ( {End =:= Total}
799 -> []
800 ; [ nl, nl,
801 'Use ?- apropos(Type:Query) or multiple words in Query '-[], nl,
802 'to restrict your search. For example:'-[], nl, nl,
803 ' ?- apropos(iso:open).'-[], nl,
804 ' ?- apropos(\'open file\').'-[]
805 ]
806 )
807 ).
808
813
814showing(Query, From, End, Total) -->
815 { End < Total,
816 apropos_uri(Query, End, URI),
817 Start is From+1
818 },
819 !,
820 [ ansi([fg(red), href(URI)], 'Showing ~D..~D of ~D matches',
821 [Start,End,Total])
822 ].
823showing(_Query, From, End, Total) -->
824 { Start is From+1 },
825 [ ansi(fg(red), 'Showing ~D..~D of ~D matches', [Start,End,Total]) ].
826
827matches([], _) --> [].
828matches([H|T], Width) -->
829 match(H, Width),
830 ( {T == []}
831 -> []
832 ; [nl],
833 matches(T, Width)
834 ).
835
836match(Obj-Summary, Width) -->
837 { Left is min(40, max(20, round(Width/3))),
838 Right is Width-Left-2,
839 man_object_summary(Obj, ObjS, Tag),
840 format(string(TagS), '~t~w~4|', [Tag]),
841 string_length(ObjS, LenObj),
842 Spaces0 is Left - LenObj - 5,
843 ( Spaces0 > 0
844 -> Spaces = Spaces0,
845 SummaryLen = Right
846 ; Spaces = 1,
847 SummaryLen is Right + Spaces0 - 1
848 ),
849 truncate(Summary, SummaryLen, SummaryE),
850 match_attributes(Obj, Attrs)
851 },
852 [ ansi([fg(default)], '~w ', [TagS]),
853 ansi(Attrs, '~w', [ObjS]),
854 '~|~*+~w'-[Spaces, SummaryE]
856 ].
857
862
863match_attributes(Obj, [fg(default), href(URI)]) :-
864 current_prolog_flag(hyperlink_term, true),
865 man_object_uri(Obj, URI),
866 !.
867match_attributes(_Obj, [fg(default)]).
868
869truncate(Summary, Width, SummaryE) :-
870 string_length(Summary, SL),
871 SL > Width,
872 !,
873 ellipsis(Ellipsis, Len),
874 Pre is max(0, Width-Len),
875 sub_string(Summary, 0, Pre, _, S1),
876 string_concat(S1, Ellipsis, SummaryE).
877truncate(Summary, _, Summary).
878
884
885ellipsis(" \u2026", 2) :-
886 stream_property(user_error, encoding(Enc)),
887 unicode_encoding(Enc),
888 !.
889ellipsis(" ...", 4).
890
891unicode_encoding(utf8).
892unicode_encoding(unicode_be).
893unicode_encoding(unicode_le).
894unicode_encoding(wchar_t).
895
900
901man_object_summary(section(_Level, _Num, Label, _File), Text, 'SEC') :-
902 atom_concat('sec:', Name, Label),
903 !,
904 format(string(Text), '~w', [Name]).
905man_object_summary(section(0, _Num, File, _Path), Text, 'SEC') :- !,
906 format(string(Text), '~w', [File]).
907man_object_summary(c(Name), Text, 'C') :- !,
908 format(string(Text), '~w()', [Name]).
909man_object_summary(xpce(Class, Kind, Name), Text, 'XPCE') :- !,
910 xpce_object_label(xpce(Class, Kind, Name), Label),
911 format(string(Text), '~w', [Label]).
912man_object_summary(f(Name/Arity), Text, 'F') :- !,
913 format(string(Text), '~p', [Name/Arity]).
914man_object_summary(Obj, Text, Tag) :-
915 ( object_class(Obj, Class),
916 class_tag(Class, Tag)
917 -> true
918 ; Tag = '?'
919 ),
920 format(string(Text), '~p', [Obj]).
921
922 925
926sandbox:safe_primitive(prolog_help:apropos(_)).
927sandbox:safe_primitive(prolog_help:apropos(_,_)).
928sandbox:safe_primitive(prolog_help:help(_))