View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2006-2018, University of Amsterdam
    7                              CWI, Amsterdam
    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(pldoc_search,
   37          [ search_form//1,             % +Options, //
   38            search_reply//2,            % +Search, +Options, //
   39            matching_object_table//2    % +Objects, +Options, //
   40          ]).   41:- use_module(library(http/html_write)).   42:- use_module(library(http/html_head)).   43:- use_module(library(dcg/basics)).   44:- use_module(library(option)).   45:- use_module(library(pairs)).   46:- use_module(library(uri)).   47:- use_module(library(debug)).   48:- use_module(library(apply)).   49:- use_module(library(lists)).   50:- use_module(library(atom)).   51:- use_module(library(porter_stem)).   52
   53:- use_module(doc_process).   54:- use_module(doc_html).   55:- use_module(doc_index).   56:- use_module(doc_util).   57:- use_module(doc_words).   58:- use_module(man_index).   59
   60:- include(hooks).

Search form and reply

To be done
- Advanced search field
  • Limit to a directory
  • Whole-word search

*/

   70:- predicate_options(search_form//1, 1,
   71                     [ for(atom),
   72                       search_in(oneof([all,noapp,app,man])),
   73                       search_match(oneof([name,summary])),
   74                       search_options(boolean)
   75                     ]).   76:- predicate_options(search_reply//2, 2,
   77                     [ resultFormat(oneof([summary,long])),
   78                       search_in(oneof([all,noapp,app,man])),
   79                       search_match(oneof([name,summary])),
   80                       header(boolean),
   81                       private(boolean),
   82                       edit(boolean),
   83                       page(positive_integer),
   84                       per_page(positive_integer),
   85                       pass_to(pldoc_index:doc_links//2, 2)
   86                     ]).
 search_form(+Options)//
Create a search input field. The input field points to /search?for=String on the current server. Options:
   95search_form(Options) -->
   96    { (   option(for(Value), Options)
   97      ->  Extra = [value(Value)]
   98      ;   Extra = []
   99      ),
  100      option(search_in(In), Options, all),
  101      option(search_match(Match), Options, summary)
  102    },
  103    html(form([ id('search-form'),
  104                action(location_by_id(pldoc_search))
  105              ],
  106              [ div([ \search_field([ name(for),
  107                                      id(for)
  108                                    | Extra
  109                                    ])
  110                    ]),
  111                \search_options(In, Match, Options)
  112              ])).
  113
  114search_options(In, Match, Options) -->
  115    { option(search_options(false), Options) },
  116    !,
  117    hidden(in, In),
  118    hidden(match, Match).
  119search_options(In, Match, _Options) -->
  120    html(div(class('search-options'),
  121             [ span(class('search-in'),
  122                    [ \radio(in, all, 'All', In),
  123                      \radio(in, app, 'Application', In),
  124                      \radio(in, man, 'Manual', In)
  125                    ]),
  126               span(class('search-match'),
  127                    [ \radio(match, name, 'Name', Match),
  128                      \radio(match, summary, 'Summary', Match)
  129                    ]),
  130               span(class('search-help'),
  131                    [ a(href(location_by_id(pldoc_package)+'pldoc.html#sec:browser'),
  132                        'Help')
  133                    ])
  134             ])).
 search_field(+Options)// is det
Hookable predicate to display the search field. Hookability is provided to experiment with auto-completion outside this package.
  143search_field(Options) -->
  144    prolog:doc_search_field(Options),
  145    !.
  146search_field(Options) -->
  147    html([ input(Options, []),
  148           input([ id('submit-for'),
  149                   type(submit),
  150                   value('Search')
  151                 ])
  152         ]).
  153
  154radio(Radio, Field, Label, In) -->
  155    {   Field == In
  156    ->  Extra = [checked]
  157    ;   Extra = []
  158    },
  159    html([ input([ type(radio),
  160                   name(Radio),
  161                   value(Field)
  162                 | Extra
  163                 ]),
  164           Label
  165         ]).
  166
  167hidden(Name, Value) -->
  168    html(input([type(hidden), name(Name), value(Value)])).
 search_reply(+For, +Options)// is det
Generate a reply searching for For. Options include
resultFormat(Format)
If summary (default), produce a summary-table. If long, produce full object descriptions.
search_in(In)
Determine which databases to search. One of all, app, man
private(Boolean)
If false (default true), hide private predicates from results.
search_match(Match)
What part of the object to match. One of name, summary
header(+Boolean)
If false, suppress the header.
per_page(+positive_integer)
Number of results per page (default 25).
page(+positive_integer)
Page number to show results for (default 1).
  199:- html_meta
  200    search_header(+, html, +, ?, ?).  201
  202search_reply(For, Options) -->
  203    { var(For) ; For == '' },
  204    !,
  205    search_header('', 'Using PlDoc search', Options),
  206    html([ ul( class('search-help'),
  207               [ li([ 'If you pause typing, the search box will display ',
  208                      'an auto completion list.  Selecting an object jumps ',
  209                      'immediately to the corresponding documentation.'
  210                    ]),
  211                 li([ 'Searching for ', i('Name/Arity'), ', ',
  212                      i('Name//Arity'), ', ', i('Name'), ' or ',
  213                      i('C-function()'), ' ensures that ',
  214                      'matching definitions appear first in the search ',
  215                      'results'
  216                    ]),
  217                 li([ 'Other searches search through the name and summary ',
  218                      'descriptions in the manual.'
  219                    ])
  220               ])
  221         ]).
  222search_reply(For, Options) -->
  223    { cached_search(For, PerCategory, Time, Options),
  224      PerCategory \== [],
  225      page_location(PerCategory, NPages, Offset, Limit, Options),
  226      option(resultFormat(Format), Options, summary)
  227    },
  228    !,
  229    search_header(For, [ 'Search results for ',
  230                         span(class(for), ['"', For, '"'])
  231                       ],
  232                  Options),
  233    { DisplayOptions = [ for(For),
  234                         cputime(Time),
  235                         page_count(NPages)
  236                       | Options
  237                       ]
  238    },
  239    indexed_matches(Format, PerCategory, Offset, Limit, DisplayOptions),
  240    search_pagination(DisplayOptions).
  241search_reply(For, Options) -->
  242    search_header(For, 'No matches', Options),
  243    html(div(class('search-no-matches'), 'No matches')).
  244
  245:- dynamic
  246    cached_search_result/4.  247
  248cached_search(For, Result, Time, Options) :-
  249    option(search_in(In), Options, all),
  250    option(search_match(Match), Options, summary),
  251    cached_search_result(For, In, Match, Result),
  252    !,
  253    Time = cached.
  254cached_search(For, Result, Time, Options) :-
  255    option(search_in(In), Options, all),
  256    option(search_match(Match), Options, summary),
  257    statistics(cputime, T0),
  258    search_doc(For, PerCategory0, Options),
  259    order_matches(PerCategory0, Result),
  260    statistics(cputime, T1),
  261    Time is T1-T0,
  262    assertz(cached_search_result(For, In, Match, Result)),
  263    prune_search_cache.
  264
  265prune_search_cache :-
  266    (   predicate_property(cached_search_result(_,_,_,_),
  267                           number_of_clauses(Count)),
  268        Del is Count - 25,
  269        Del > 0
  270    ->  forall(between(1,Del,_), retract(cached_search_result(_,_,_,_)))
  271    ;   true
  272    ).
  273
  274page_location(PerCategory, NPages, Offset, Limit, Options) :-
  275    option(page(Page), Options, 1),
  276    option(per_page(Limit), Options, 25),
  277    Offset is (Page-1)*Limit,
  278    count_matches(PerCategory, Total),
  279    NPages is (Total+Limit-1)//Limit.
  280
  281search_pagination(Options) -->
  282    { option(page(Page), Options, 1),
  283      option(page_count(NPages), Options, 1)
  284    },
  285    html(div(class(pagination),
  286             [ \search_prev(Page, Options),
  287               span(class(current), ['Page ', Page, ' of ', NPages]),
  288               \search_next(NPages, Page, Options)
  289             ])).
  290
  291search_prev(Page, _) -->
  292    { Page =:= 1 },
  293    !.
  294search_prev(Page, Options) -->
  295    { Prev is Page - 1,
  296      page_link(Prev, Link, Options)
  297    },
  298    html(a(href(Link), '< Prev')).
  299
  300search_next(NPages, Page, _) -->
  301    { Page =:= NPages, ! }, [].
  302search_next(_NPages, Page, Options) -->
  303    { Next is Page + 1,
  304      page_link(Next, Link, Options)
  305    },
  306    html(a(href(Link), 'Next >')).
  307
  308page_link(Page, '?'+QueryString, Options) :-
  309    option(for(For), Options),
  310    option(search_in(In), Options, all),
  311    option(search_match(Match), Options, summary),
  312    option(resultFormat(Format), Options, summary),
  313    uri_query_components(QueryString,
  314                         [ for(For),
  315                           in(In),
  316                           match(Match),
  317                           resultFormat(Format),
  318                           page(Page)
  319                         ]).
  320
  321search_header(_For, _Title, Options) -->
  322    { option(header(false), Options) },
  323    !,
  324    html_requires(pldoc).
  325search_header(For, Title, Options) -->
  326    html_requires(pldoc),
  327    doc_links('', [for(For)|Options]),
  328    html(h1(class('search-results'), Title)).
 matching_object_table(+Objects, +Options)// is det
Show a list of matching objects, similar to a result-set from search.
  335matching_object_table(Objects, Options) -->
  336    { maplist(obj_cat_sec, Objects, Pairs),
  337      group_hits(Pairs, Organized),
  338      option(format(Format), Options, summary)
  339    },
  340    indexed_matches(Format, Organized, Options).
  341
  342obj_cat_sec(Object, Cat-(Section-Object)) :-
  343    prolog:doc_object_summary(Object, Cat, Section, _Summary).
  344
  345indexed_matches(Format, PerCategory, Offset, Limit, Options) -->
  346    { cat_offset(Offset, _,PerCategory, PerCategory1),
  347      cat_limit(Limit, _, PerCategory1, PerCategory2)
  348    },
  349    (   { PerCategory2 == PerCategory }
  350    ->  indexed_matches(Format, PerCategory, Options)
  351    ;   category_counts(PerCategory,
  352                        [ showing('Total'),
  353                          link(category)
  354                        | Options
  355                        ]),
  356        { delete(Options, cputime(_), Options1) },
  357        category_counts(PerCategory2,
  358                        [ showing('Showing'),
  359                          class([showing])
  360                        | Options1
  361                        ]),
  362        search_pagination(Options),
  363        matches(Format, PerCategory2, Options)
  364    ).
 cat_offset(+Offset, -Remaining, +PerCat0, -PerCat) is det
 cat_limit(+Limit, -Remaining, +PerCat0, -PerCat) is det
  369cat_offset(0, 0, PerCat, PerCat) :-
  370    !.
  371cat_offset(N, R, [C-H|T], PerCat) :-
  372    H = [_-[_|_]|_],
  373    !,
  374    cat_offset(N, R1, H, H1),
  375    (   H1 == []
  376    ->  !, cat_offset(R1, R, T, PerCat)
  377    ;   PerCat = [C-H1|T]
  378    ).
  379cat_offset(N, R, [_C-L|T0], PerCat) :-
  380    length(L, Len),
  381    Left is N-Len,
  382    Left > 0,
  383    !,
  384    cat_offset(Left, R, T0, PerCat).
  385cat_offset(N, 0, [C-L0|T], [C-L|T]) :-
  386    !,
  387    length(Skip, N),
  388    append(Skip, L, L0).
  389cat_offset(N, N, Obj, Obj).
  390
  391cat_limit(0, 0, _PerCat, []) :-
  392    !.
  393cat_limit(N, R, [C-H|T], PerCat) :-
  394    H = [_-[_|_]|_],
  395    !,
  396    cat_limit(N, R1, H, H1),
  397    (   R1 == 0
  398    ->  PerCat = [C-H1]
  399    ;   PerCat = [C-H|T1],
  400        cat_limit(R1, R, T, T1)
  401    ).
  402cat_limit(N, R, [C-L|T0], [C-L|T]) :-
  403    length(L, Len),
  404    More is N - Len,
  405    More >= 0,
  406    !,
  407    cat_limit(More, R, T0, T).
  408cat_limit(N, 0, [C-L0|_], [C-L]) :-
  409    !,
  410    length(L, N),
  411    append(L, _, L0).
  412cat_limit(N, N, [], []).
 order_matches(+PerCat, -Ordered) is det
Order matches per category. Each low level object is of the shape q(Q, Object), where Q is a number between 0 and 1, 1 implying a perfect fit.
  421order_matches(PerCat0, PerCat) :-
  422    maplist(order_category, PerCat0, PerCat).
  423
  424order_category(Cat-PerSection0, Cat-PerSection) :-
  425    maplist(order_section, PerSection0, PerSectionTagged),
  426    sort(1, >=, PerSectionTagged, Ordered),
  427    pairs_values(Ordered, PerSection).
  428
  429order_section(Section-Objects0, Q-(Section-Objects)) :-
  430    sort(1, >=, Objects0, Objects),
  431    maplist(arg(1), Objects, QList),
  432    join_quality(QList, Q).
  433
  434join_quality([], 0).
  435join_quality([Q], Q).
  436join_quality([QH|QL], Q) :-
  437    join_quality(QL, QT),
  438    Q is 1-(1-QH)*(1-QT).
 indexed_matches(+Format, +PerCategory, +Options)//
Emit the matches.
  445indexed_matches(Format, PerCategory, Options) -->
  446    category_counts(PerCategory, Options),
  447    matches(Format, PerCategory, Options).
  448
  449category_counts(PerCategory, Options) -->
  450    { count_matches(PerCategory, Matches),
  451      option(class(Classes), Options, []),
  452      (   PerCategory = [_]
  453      ->  merge_options([link(false)], Options, Options1)
  454      ;   Options1 = Options
  455      )
  456    },
  457    html([ div(class(['search-counts'|Classes]),
  458               [ \category_showing(Options1),
  459                 Matches,
  460                 \count_by_category(PerCategory, Options1),
  461                 \search_time(Options1)
  462               ])
  463         ]).
  464
  465count_by_category([Cat-_PerFile], Options) -->
  466    !,
  467    html(' matches from '),
  468    category_link(Cat, Options).
  469count_by_category(PerCategory, Options) -->
  470    html(' matches; '),
  471    count_by_category_list(PerCategory, Options).
  472
  473count_by_category_list([], _) -->
  474    [].
  475count_by_category_list([Cat-PerFile|T], Options) -->
  476    { count_category(PerFile, Count) },
  477    html([ \category_link(Cat, Options), ': ', Count ]),
  478    (   {T == []}
  479    ->  []
  480    ;   html(', '),
  481        count_by_category_list(T, Options)
  482    ).
  483
  484count_matches([], 0).
  485count_matches([_-Cat|T], Count) :-
  486    count_matches(T, Count0),
  487    count_category(Cat, N),
  488    Count is Count0 + N.
  489
  490count_category([], 0).
  491count_category([_-Objs|T], Count) :-
  492    count_category(T, Count0),
  493    length(Objs, N),
  494    Count is Count0 + N.
  495
  496category_showing(Options) -->
  497    { option(showing(Showing), Options) },
  498    html(span(class('search-showing'), [Showing, :])).
  499category_showing(_) -->
  500    [].
  501
  502search_time(Options) -->
  503    { option(cputime(Time), Options) },
  504    !,
  505    (   { number(Time) }
  506    ->  html(span(class('search-time'), '(~2f sec.)'-[Time]))
  507    ;   html(span(class('search-time'), '(~w)'-[Time]))
  508    ).
  509search_time(_) -->
  510    [].
 matches(+Format, +PerCategory, +Options)// is det
Display search matches according to Format.
Arguments:
PerCategory- List of File-Objects
  518matches(long, PerCategory, Options) -->
  519    long_matches_by_type(PerCategory, Options).
  520matches(summary, PerCategory, Options) -->
  521    html(table(class(summary),
  522               \short_matches_by_type(PerCategory, 1, Options))).
  523
  524
  525long_matches_by_type([], _) -->
  526    [].
  527long_matches_by_type([Category-PerFile|T], Options) -->
  528    category_header(Category, Options),
  529    long_matches(PerFile, Options),
  530    long_matches_by_type(T, Options).
  531
  532
  533long_matches([], _) -->
  534    [].
  535long_matches([File-Objs|T], Options) -->
  536    file_header(File, Options),
  537    objects(Objs, Options),
  538    long_matches(T, Options).
  539
  540category_header(Category, _Options) -->
  541    html(h1(class(category), \category_title(Category))).
  542
  543short_matches_by_type([], _, _) -->
  544    [].
  545short_matches_by_type([Category-PerFile|T], Nth, Options) -->
  546    category_index_header(Category, Nth, Options),
  547    short_matches(PerFile, Options),
  548    { succ(Nth, Nth1) },
  549    short_matches_by_type(T, Nth1, Options).
  550
  551short_matches([], _) -->
  552    [].
  553short_matches([File-Objs|T], Options) -->
  554    file_index_header(File, Options),
  555    object_summaries(Objs, File, Options),
  556    short_matches(T, Options).
  557
  558
  559category_index_header(Category, Nth, _Options) -->
  560    (   { Nth > 1 }
  561    ->  category_sep('category-top-sep')
  562    ;   []
  563    ),
  564    html(tr(th([class(category), colspan(3)],
  565               a(name(Category), \category_title(Category))))),
  566    category_sep('category-bottom-sep').
  567
  568category_sep(Which) -->
  569    html(tr(th([class(Which), colspan(3)],
  570               &(nbsp)))).
  571
  572category_link(Category, Options) -->
  573    { option(link(false), Options) },
  574    !,
  575    category_title(Category).
  576category_link(Category, Options) -->
  577    { option(link(category), Options),
  578      category_link(Category, HREF, Options)
  579    },
  580    !,
  581    html(a(href(HREF), \category_title(Category))).
  582category_link(Category, _Options) -->
  583    { atom_concat(#, Category, HREF) },
  584    html(a(href(HREF), \category_title(Category))).
  585
  586category_link(Category, '?'+QueryString, Options) :-
  587    (   category_abbreviation(Category, Abbrev)
  588    ->  true
  589    ;   Abbrev = Category
  590    ),
  591    option(for(For), Options),
  592    option(search_match(Match), Options, summary),
  593    option(resultFormat(Format), Options, summary),
  594    uri_query_components(QueryString,
  595                         [ for(For),
  596                           in(Abbrev),
  597                           match(Match),
  598                           resultFormat(Format)
  599                         ]).
  600
  601category_title(Category) -->
  602    {   prolog:doc_category(Category, _Order, Title)
  603    ->  true
  604    ;   Title = Category
  605    },
  606    html(Title).
 search_doc(+SearchString, -PerType:list, +Options) is det
Return matches of SearchString as Type-PerFile tuples, where PerFile is a list File-ListOfObjects.
  613search_doc(Search, PerType, Options) :-
  614    findall(Tuples, matching_object(Search, Tuples, Options), Tuples0),
  615    sort(Tuples0, Tuples),
  616    group_hits(Tuples, PerType0),
  617    prune_library(PerType0, PerType).
  618
  619group_hits(Tuples, PerType) :-
  620    keysort(Tuples, Tuples1),
  621    group_pairs_by_key(Tuples1, PerCat0),
  622    key_sort_order(PerCat0, PerCat1),
  623    keysort(PerCat1, PerCat2),
  624    pairs_values(PerCat2, PerCat),
  625    group_by_file(PerCat, PerType).
  626
  627key_sort_order([], []).
  628key_sort_order([Cat-ByCat|T0], [Order-(Cat-ByCat)|T]) :-
  629    (   prolog:doc_category(Cat, Order, _Title)
  630    ->  true
  631    ;   Order = 99
  632    ),
  633    key_sort_order(T0, T).
  634
  635
  636group_by_file([], []).
  637group_by_file([Type-Tuples0|T0], [Type-ByFile|T]) :-
  638    keysort(Tuples0, Tuples),
  639    group_pairs_by_key(Tuples, ByFile),
  640    group_by_file(T0, T).
 prune_library(+PerCat0, -PerCat) is det
Prune objects from the libary that also appear in the manual.
  647prune_library(PerCat0, PerCat) :-
  648    selectchk(library-InLib0, PerCat0, library-InLib, PerCat1),
  649    !,
  650    (   cat_objects(manual, PerCat0, Manual),
  651        cat_objects(packages, PerCat0, Packages),
  652        append(Manual, Packages, Objects),
  653        sort(Objects, OSet0),
  654        maplist(arg(2), OSet0, OSet),       % get rid of q(Q,Obj)
  655        convlist(prune_section(OSet), InLib0, InLib),
  656        InLib \== []
  657    ->  PerCat = PerCat1
  658    ;   selectchk(library-_, PerCat0, PerCat)
  659    ).
  660prune_library(PerCat, PerCat).
  661
  662cat_objects(Cat, PerCat, Objects) :-
  663    memberchk(Cat-Sections, PerCat),
  664    !,
  665    pairs_values(Sections, NestedObjects),
  666    append(NestedObjects, Objects).
  667cat_objects(_, _, []).
  668
  669prune_section(Prune, Section-Objects0, Section-Objects) :-
  670    exclude(in_set(Prune), Objects0, Objects),
  671    Objects \== [].                     % specific library becomes empty
  672
  673in_set(Prune, q(_Q,Obj)) :-
  674    memberchk(Obj, Prune),
  675    !.
  676in_set(Prune, q(_Q,_Module:Obj)) :-
  677    memberchk(Obj, Prune).
 matching_object(+SearchString, -Object, +Options) is nondet
Object matches SearchString. Options include
search_in(In)
One of all, app, man.
search_match(Match)
One of name, summary
Arguments:
Object- Term of the form File-Item
To be done
- Deal with search syntax
  693matching_object(Search, Type-(Section-q(1,Obj)), Options) :-
  694    atom_concat(Function, '()', Search),
  695    Obj = c(Function),
  696    option(search_in(In), Options, all),
  697    prolog:doc_object_summary(Obj, Type, Section, _),
  698    matching_category(In, Type).
  699matching_object(Search, Type-(Section-q(1,Obj)), Options) :-
  700    (   atom_pi(Search, Obj0)
  701    ->  ground(Obj0)
  702    ;   catch(atom_to_term(Search, Obj0, _), _, fail),
  703        nonvar(Obj0)
  704    ),
  705    opt_qualify(Obj0, Obj),
  706    option(search_in(In), Options, all),
  707    prolog:doc_object_summary(Obj, Type, Section, _),
  708    matching_category(In, Type).
  709matching_object(Search, Match, Options) :-
  710    atom_codes(Search, Codes),
  711    phrase(search_spec(For0), Codes),
  712    (   For0 = not(_)
  713    ->  throw(error(bad_search(only_not), _))
  714    ;   optimise_search(For0, For),
  715        exec_search(For, Match, Options)
  716    ).
  717
  718opt_qualify(Obj0, Obj) :-
  719    Obj0 = _:_,
  720    !,
  721    Obj = Obj0.
  722opt_qualify(Obj, Obj).
  723opt_qualify(Obj, _:Obj).
 optimise_search(+Spec, -Optimised)
Optimise a search specification. Currently only deals with the simple case of first searching for a negation and then a positive term.
  732optimise_search(and(not(A0), B0), and(B, not(A))) :-
  733    !,
  734    optimise_search(A0, A),
  735    optimise_search(B0, B).
  736optimise_search(A, A).
 exec_search(+Spec, -Match, +Options) is nondet
Spec is one of
and(Spec, Spec)
Intersection of the specification
not(Spec)
Negation of the specification
quoted(Tokens)
A quoted list of tokens.
  750exec_search(Spec, Match, Options) :-
  751    exec_search(Spec, Match0, Q, Options),
  752    add_quality(Match0, Q, Match).
  753
  754add_quality(Type-(Section-Obj), Q, Type-(Section-q(Q,Obj))).
  755
  756exec_search(and(A, B), Match, Q, Options) :-
  757    !,
  758    exec_search(A, Match, Q1, Options),
  759    exec_search(B, Match, Q2, Options),
  760    Q is 1-((1-Q1)*(1-Q2)).
  761exec_search(Search, Type-(Section-Obj), Q, Options) :-
  762    option(search_in(In), Options, all),
  763    option(search_match(Match), Options, summary),
  764    option(private(Public), Options, true),
  765    prolog:doc_object_summary(Obj, Type, Section, Summary),
  766    matching_category(In, Type),
  767    match_private(Public, Obj),
  768    (   Search = not(For)
  769    ->  State = s(0),
  770        \+ ( match_object(For, Obj, Summary, Match, Q),
  771             nb_setarg(1, State, Q)
  772           ),
  773        arg(1, State, Q)
  774    ;   match_object(Search, Obj, Summary, Match, Q)
  775    ).
  776
  777
  778matching_category(all, _).
  779matching_category(noapp, Category) :-
  780    !,
  781    Category \== application.
  782matching_category(Category, Category).
  783matching_category(Abbrev, Category) :-
  784    category_abbreviation(Category, Abbrev).
  785
  786category_abbreviation(application, app).
  787category_abbreviation(manual,      man).
  788category_abbreviation(library,     lib).
  789category_abbreviation(packages,    pack).
  790category_abbreviation(wiki,        wiki).
  791
  792match_private(true, _).
  793match_private(false, Object) :-
  794    (   Object = (Module:PI)
  795    ->  current_module(Module),
  796        pi_head(PI, Head),
  797        (   predicate_property(Module:Head, exported)
  798        ->  true
  799        ;   predicate_property(Module:Head, multifile)
  800        ->  true
  801        ;   predicate_property(Module:Head, public)
  802        )
  803    ;   true
  804    ).
  805
  806pi_head(Name/Arity, Head) :-
  807    functor(Head, Name, Arity).
  808pi_head(Name//Arity, Head) :-
  809    Arity1 is Arity+ 2,
  810    functor(Head, Name, Arity1).
 search_spec(-Search)// is det
Break a search string from the user into a logical expression.
  816search_spec(Spec) -->
  817    blanks,
  818    prim_search_spec(A),
  819    blanks,
  820    (   eos
  821    ->  { Spec = A }
  822    ;   search_spec(B)
  823    ->  { Spec = and(A,B) }
  824    ).
  825
  826prim_search_spec(quoted(Quoted)) -->
  827    "\"", string(Codes), "\"",
  828    !,
  829    { tokenize_atom(Codes, Quoted)
  830    }.
  831prim_search_spec(Spec) -->
  832    nonblanks(Codes),
  833    {   Codes = [0'-,C0|Rest],
  834        code_type(C0, csym)
  835    ->  atom_codes(Word, [C0|Rest]),
  836        Spec = not(Word)
  837    ;   Codes \== [],
  838        atom_codes(Spec, Codes)
  839    }.
 object_summary(?Object, ?Category, ?Section, ?Summary) is nondet
True if Object is summarised by Summary. This multifile predicate can be extended with other search mechanisms. The returned objects must be handled by object_summaries//2 and objects//2.
Arguments:
Category- Atom describing the source.
Section- Reference to the context of Object.
  852prolog:doc_object_summary(Obj, Category, File, Summary) :-
  853    once(prolog_object(Obj)),
  854    current_prolog_flag(home, SWI),
  855    doc_comment(Obj0, File:_Line, Summary, _Comment),
  856    (   is_list(Obj0)
  857    ->  member(Obj, Obj0)
  858    ;   Obj = Obj0
  859    ),
  860    Obj \= _:module(_Title),                % HACK.  See ref_object//1
  861    (   sub_atom(File, 0, _, _, SWI)
  862    ->  Category = library
  863    ;   Category = application
  864    ).
  865
  866prolog_object(Var) :- var(Var), !.
  867prolog_object(_/_).
  868prolog_object(_//_).
  869prolog_object(_:_/_).
  870prolog_object(_:_//_).
  871prolog_object(module(_)).
 doc_category(Name, SortOrder, Description) is nondet
Describe the various categories of search results. Used to create the category headers as well as the advanced search dialog.
Arguments:
SortOrder- Ranges 0..100. Lower values come first
  882prolog:doc_category(application, 20, 'Application').
  883prolog:doc_category(library,     80, 'System Libraries').
  884
  885
  886                 /*******************************
  887                 *             UTIL             *
  888                 *******************************/
 match_object(+For, +Object, +Summary, +How, -Quality) is semidet
True when Object with summary text Summary matches For acording to How.
Arguments:
For- is either a token (atom) or a term quoted(Tokens), where Tokens is a list of atoms.
How- is one of name or summary
Quality- is a number in the range 0..1, where 1 means a strong match.
  901match_object(For, Object, Summary, How, Quality) :-
  902    (   doc_object_identifier(Object, Identitier),
  903        identifier_match_quality(For, Identitier, Quality)
  904    ->  debug(search(rank), 'Rank "~w" in identifier "~w": ~q',
  905              [For, Identitier, Quality])
  906    ;   How == summary,
  907        summary_match_quality(For, Summary, Quality),
  908        debug(search(rank), 'Rank "~w" in summary "~w": ~q',
  909              [For, Summary, Quality])
  910    ).
  911
  912summary_match_quality(For, Summary, Q) :-
  913    tokenize_atom(Summary, Tokens0),
  914    exclude(is_punctuation, Tokens0, Tokens),
  915    Tokens \== [],
  916    token_match_quality(summary, For, Tokens, Q0),
  917    Q is Q0/2.
  918
  919is_punctuation(Token) :-
  920    atom_length(Token, 1),
  921    char_type(Token, punct).
  922
  923
  924identifier_match_quality(Identifier, Identifier, 1) :-
  925    !.
  926identifier_match_quality(For, Identifier, Q) :-
  927    dwim_match(For, Identifier, _),
  928    !,
  929    Q = 0.8.
  930identifier_match_quality(For, Identifier, Q) :-
  931    identifier_parts(Identifier, Parts),
  932    Parts \== [],
  933    token_match_quality(identifier, For, Parts, Q).
  934
  935token_match_quality(_How, quoted(Tokens), Parts, Q) :-
  936    !,
  937    append(Tokens, _, All),
  938    append(_, All, Parts),
  939    Q = 1.
  940token_match_quality(How, For, Parts, Q) :-
  941    length(Parts, Len),
  942    (   memberchk(For, Parts)
  943    ->  Q0 = 1
  944    ;   porter_stem(For, Stem),
  945        member(Part, Parts),
  946        porter_stem(Part, Stem)
  947    ->  Q0 = 0.9
  948    ;   How == summary,
  949        member(Part, Parts),
  950        sub_atom_icasechk(Part, _, For),
  951        identifier_parts(Part, SubParts),
  952        token_match_quality(identifier, For, SubParts, Q00)
  953    ->  Q0 is Q00/2
  954    ;   How == summary,
  955        member(Part, Parts),
  956        sub_atom_icasechk(Part, 0, For),
  957        is_numbered_var(Part, For)
  958    ->  Q0 is 0.9
  959    ;   doc_related_word(For, Word, Distance),
  960        memberchk(Word, Parts)
  961    ->  Q0 = Distance
  962    ),
  963    Q is Q0/Len.
  964
  965is_numbered_var(VarName, Search) :-
  966    atom_length(Search, Len),
  967    sub_string(VarName, Len, _, 0, NS),
  968    number_string(_, NS),
  969    sub_atom(VarName, 0, 1, _, First),
  970    char_type(First, prolog_var_start)