View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        jan@swi-prolog.org
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2018-2026, CWI Amsterdam
    7			      SWI-Prolog Solutions b.v.
    8    All rights reserved.
    9
   10    Redistribution and use in source and binary forms, with or without
   11    modification, are permitted provided that the following conditions
   12    are met:
   13
   14    1. Redistributions of source code must retain the above copyright
   15       notice, this list of conditions and the following disclaimer.
   16
   17    2. Redistributions in binary form must reproduce the above copyright
   18       notice, this list of conditions and the following disclaimer in
   19       the documentation and/or other materials provided with the
   20       distribution.
   21
   22    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   23    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   24    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   25    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   26    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   27    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   28    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   29    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   30    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   31    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   32    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   33    POSSIBILITY OF SUCH DAMAGE.
   34*/
   35
   36:- module(prolog_help,
   37	  [ help/0,
   38	    help/1,                     % +Object
   39	    apropos/1,                  % +Search
   40	    apropos/2,                  % +Search, +Options
   41            help_apropos/4,
   42	    help_text/2                 % :PI, -Text:string
   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]).

Text based manual

This module provides help/1 and apropos/1 that give help on a topic or searches the manual for relevant topics.

By default the result of help/1 is sent through a pager such as less. This behaviour is controlled by the following:

   97:- meta_predicate
   98    with_pager(0).   99
  100:- multifile
  101    show_html_hook/1.  102
  103% one of `default`, `false`, an executable or executable(options), e.g.
  104% less('-r').
  105:- create_prolog_flag(help_pager, default,
  106		      [ type(term),
  107			keep(true)
  108		      ]).
 help is det
 help(+What) is det
Show help for What. What is a term that describes the topics(s) to give help for. Notations for What are:
Atom
This ambiguous form is most commonly used and shows all matching documents. For example:
?- help(append).
Name / Arity
Give help on predicates with matching Name/Arity. Arity may be unbound.
Name // Arity
Give help on the matching DCG rule (non-terminal)
Module:Name
Give help on predicates with Name in Module and any arity. Used for loaded code only.
Module:Name/Arity
Give help on predicates with Name in Module and Arity. Used for loaded code only.
f(Name/Arity)
Give help on the matching Prolog arithmetic functions.
c(Name)
Give help on the matching C interface function
section(Label)
Show the section from the manual with matching Label.
xpce(Class, Kind, Name)
Show the documentation of an XPCE class member.

help/1 shows documentation from the manual as well as from loaded user code if the code is documented using PlDoc. To show only the documentatoion of the loaded predicate we may prefix predicate indicator with the module in which it is defined.

If an exact match fails this predicates attempts fuzzy matching and, when successful, display the results headed by a warning that the matches are based on fuzzy matching.

If possible, the results are sent through a pager such as the less program. This behaviour is controlled by the Prolog flag help_pager. See section level documentation.

If the terminal supports hyperlinks (see the Prolog flag hyperlink_term), the manual references in the page are clickable. In an Epilog window, clicking one quits the pager and runs help/1 on the linked object.

See also
- apropos/1 for searching the manual names and summaries.
  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).
 show_html_hook(+HTML:string) is semidet
Hook called to display the extracted HTML document. If this hook fails the HTML is rendered to the console as plain text using html_text/2.
  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)).
 tty_width(-Width) is det
Return the believed width of the terminal. If we do not know Width is bound to 80.
  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)).
  304% resolved manual objects, e.g. from a clicked hyperlink.  See man_link/2.
  305help_object(Obj, _How, Obj, ID) :-
  306    man_object_id(Obj, ID).
  307% for currently loaded predicates
  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,_), _), % not a section
  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).
 man_object_id(@Object, -ID) is semidet
True when Object is a fully specified manual object with identifier ID. Predicate indicators are not included: these are ambiguous enough to be handled by the fuzzy matching clauses above.
  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)).
 current_predicate_help(?PI) is nondet
True when we have documentation on PI. First we decide we have a definition for PI, then we check whether or not we have documentation for the module in which PI resides. If not, we switch to documentation collect mode and reload the file that defines PI.
  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:_, _)             % Some predicates are documented
  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, _).             % Test that our predicate is documented
  379
  380match_name(exact, Name, Name).
  381match_name(dwim,  Name, Fuzzy) :-
  382    freeze(Fuzzy, dwim_match(Fuzzy, Name)).
 main_source_file(+Pred, -File) is semidet
True when File is the main (not included) file that defines Pred.
  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).
 with_pager(+Goal)
Send the current output of Goal through a pager. If no pager can be found we simply dump the output to the current output. We wait for the pager to terminate, so the toplevel does not print its prompt on the screen the pager is using.
  408with_pager(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).
 pager(?Thread, ?PID) is nondet
True while Thread is showing help using the pager process PID. Used by quit_pager/1 to get the pager out of the way if the user clicks a hyperlink in the page it is showing.
  425:- dynamic
  426    pager/2.                            % Thread, PID
  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))).
 pager_screen(+Screen, +Which) is det
Give the pager a screen of its own, so that quitting it leaves the terminal as it was. Windows only: a pager there takes a screen buffer from the console API and the console swaps back to the previous one when the pager exits, but a pseudo console -- which is what an Epilog window gives its children -- does not carry those calls. Its alternate screen is the DEC private mode and nothing else, so the terminal is told here rather than by the pager.

Elsewhere the pager does this itself, from its terminal description, and a pager that does not (cat) is one whose output should stay.

  460pager_screen(_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
  474pager_ok(_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
  502pager_options(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
  510pager_default_options(less, ['-r']).
 running_under_emacs
True when we believe to be running in Emacs. Unfortunately there is no easy unambiguous way to tell.
  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    !.
 apropos(+Query) is det
 apropos(+Query, +Options) is det
Print objects from the manual whose name or summary match with Query. Query takes one of the following forms:
Type:Text
Find objects matching Text and filter the results by Type. Type matching is a case intensitive prefix match. Defined types are section, cfunction, function, iso_predicate, swi_builtin_predicate, library_predicate, dcg and aliases chapter, arithmetic, c_function, predicate, nonterminal and non_terminal. For example:
?- apropos(c:close).
?- apropos(f:min).
Text
Text is broken into tokens. A topic matches if all tokens appear in the name or summary of the topic. Matching is case insensitive. Results are ordered depending on the quality of the match.

Only the best limit matches are shown. Options:

limit(+Count)
Maximum number of matches to show. Default 20.
offset(+Skip)
Ignore the Skip best matches. Default 0.

If the terminal supports hyperlinks (see the Prolog flag hyperlink_term), the matches are clickable and so is the line that reports there are more matches. In an Epilog window, clicking these runs help/1 on the match or apropos/2 on the next page.

  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    ).
 page(+List, +From, +Limit, -Page) is det
Page is the sub list of List that starts at From and holds at most Limit elements.
  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).
 help_apropos(+Query, -Obj, -Summary, -Score) is nondet
Find matching documented objects in the help database. Obj is the formal object identifier, Summary its summary description and Score is a number indicating the quality of the match.
  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).
 help_text(+Predicate:term, -HelpText:string) is semidet
When Predicate is a term of the form Name/Arity for which documentation exists, HelpText is the documentation in textual format (parsed from the HTML help).
  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                /*******************************
  707                *            LINKS             *
  708                *******************************/
 man_link(+Term, -Mapped) is semidet
The link_scheme(man) option of man_page//2 already wrote the manual references as man: IRIs, which a terminal emits as OSC8 hyperlinks (see ansi_hyperlink/3) and tty_link_hook/2 below resolves when clicked. This maps the remaining links, which address the PlDoc server, onto the same IRIs. Links we cannot resolve are removed.
  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    ).
 apropos_uri(+Query, +Offset, -URI) is det
 apropos_uri_goal(+URI, -Goal) is semidet
Convert between an apropos: IRI and the apropos/2 goal that continues the search at Offset. Used to make the line telling there are more matches clickable.
  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).
 epilog:tty_link_hook(+Terminal, +Link) is semidet
Open a man: or apropos: link that was clicked in an Epilog Terminal. We quit the pager if it is still showing the page the link was clicked in and let the terminal run help/1 on the linked object or continue the apropos/2 search.
  750:- multifile epilog:tty_link_hook/2.  751
  752epilog:tty_link_hook(Terminal, URL) :-
  753    link_goal(URL, Goal),
  754    !,                                  % the link is ours, do not let
  755    quit_pager(Terminal),               % Epilog pass it to a browser
  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).
 quit_pager(+Terminal) is det
If the Prolog thread of Terminal is waiting for its pager, tell the pager to quit. All common pagers quit on q.
  768quit_pager(Terminal) :-
  769    get(Terminal, thread, Thread),
  770    pager(Thread, _PID),
  771    !,
  772    send(Terminal, send, "q").
  773quit_pager(_).
  774
  775		 /*******************************
  776		 *            MESSAGES		*
  777		 *******************************/
  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    ).
 showing(+Query, +From, +End, +Total)// is det
Emit the line telling which of the matches are shown. If not all matches are shown this is a link to the next page.
  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]
  855%     '~*|~w'-[Spaces, SummaryE]		% Should eventually work
  856    ].
 match_attributes(+Object, -Attributes) is det
ANSI attributes for printing Object. If the terminal supports them, make the match a link that runs help/1 on Object.
  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).
 ellipsis(-Ellipsis:string, -Length:integer) is det
Ellipsis is appended to truncated text and Length is the number of columns it occupies. Use the Unicode horizontal ellipsis if the message stream can represent it.
  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).
 man_object_summary(+Object, -Label:string, -Tag) is det
Label is the text used to display Object in the apropos output. Tag is a short indication of the type of Object.
  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		 /*******************************
  923		 *            SANDBOX		*
  924		 *******************************/
  925
  926sandbox:safe_primitive(prolog_help:apropos(_)).
  927sandbox:safe_primitive(prolog_help:apropos(_,_)).
  928sandbox:safe_primitive(prolog_help:help(_))