View source with formatted comments or as raw
    1/*  Part of XPCE --- The SWI-Prolog GUI toolkit
    2
    3    Author:        Jan Wielemaker and Anjo Anjewierden
    4    E-mail:        wielemak@science.uva.nl
    5    WWW:           http://www.swi-prolog.org/packages/xpce/
    6    Copyright (c)  2006-2015, University of Amsterdam
    7    All rights reserved.
    8
    9    Redistribution and use in source and binary forms, with or without
   10    modification, are permitted provided that the following conditions
   11    are met:
   12
   13    1. Redistributions of source code must retain the above copyright
   14       notice, this list of conditions and the following disclaimer.
   15
   16    2. Redistributions in binary form must reproduce the above copyright
   17       notice, this list of conditions and the following disclaimer in
   18       the documentation and/or other materials provided with the
   19       distribution.
   20
   21    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   22    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   23    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   24    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   25    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   26    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   27    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   28    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   29    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   30    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   31    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   32    POSSIBILITY OF SUCH DAMAGE.
   33*/
   34
   35:- module(pce_xref_gui,
   36          [ gxref/0,
   37            xref_file_imports/2,        % +File, -Imports
   38            xref_file_exports/2         % +File, -Exports
   39          ]).   40:- use_module(pce).   41:- use_module(persistent_frame).   42:- use_module(tabbed_window).   43:- use_module(toolbar).   44:- use_module(pce_report).   45:- use_module(pce_util).   46:- use_module(pce_toc).   47:- use_module(pce_arm).   48:- use_module(pce_tagged_connection).   49:- use_module(dragdrop).   50:- use_module(pce_prolog_xref).   51:- use_module(print_graphics).   52:- use_module(tabular).   53:- use_module(library(lists)).   54:- use_module(library(debug)).   55:- use_module(library(autowin)).   56:- use_module(library(broadcast)).   57:- use_module(library(prolog_source)).   58
   59version('0.1.1').
   60
   61:- dynamic
   62    setting/2.   63
   64setting_menu([ warn_autoload,
   65               warn_not_called
   66             ]).
   67
   68setting(warn_autoload,      false).
   69setting(warn_not_called,    true).
   70setting(hide_system_files,  true).
   71setting(hide_profile_files, true).
   72
   73/** <module> Cross-referencer front-end
   74
   75XPCE based font-end of the Prolog cross-referencer.  Tasks:
   76
   77        * Cross-reference currently loaded program              OK
   78        * Generate module-dependency graph                      OK
   79        * Information on
   80                - Syntax and other encountered errors
   81                - Export/Import relation between modules        OK
   82                - Undefined predicates                          OK
   83                - Unused predicates                             OK
   84        * Summary information
   85                - Syntax and other encountered errors
   86                - Exports never used (not for libs!)
   87                - Undefined predicates
   88                - Unused predicates
   89        * Export module import and export header
   90                - Using require/1
   91                - Using use_module/1
   92                - Using use_module/2                            OK
   93                - Export header for non-module files            OK
   94
   95@bug    Tool produces an error if a file that has been xref'ed is
   96        deleted.  Paulo Moura.
   97@see    library(prolog_xref) holds the actual data-collection.
   98*/
   99
  100%!  gxref
  101%
  102%   Start graphical cross-referencer on loaded program.  The GUI
  103%   is started in the XPCE thread.
  104
  105gxref :-
  106    in_pce_thread(xref_gui).
  107
  108xref_gui :-
  109    send(new(XREF, xref_frame), open),
  110    send(XREF, wait),
  111    send(XREF, update).
  112
  113
  114:- pce_begin_class(xref_frame, persistent_frame,
  115                   "GUI for the Prolog cross-referencer").
  116
  117initialise(F) :->
  118    send_super(F, initialise, 'Prolog XREF'),
  119    new(FilterDialog, xref_filter_dialog),
  120    send(new(BrowserTabs, tabbed_window), below, FilterDialog),
  121    send(BrowserTabs, left, new(WSTabs, tabbed_window)),
  122    send(BrowserTabs, name, browsers),
  123    send(BrowserTabs, hor_shrink, 10),
  124    send(BrowserTabs, hor_stretch, 10),
  125    send(WSTabs, name, workspaces),
  126    send_list([BrowserTabs, WSTabs], label_popup, F?tab_popup),
  127    send(new(TD, tool_dialog(F)), above, BrowserTabs),
  128    send(new(report_dialog), below, BrowserTabs),
  129    send(F, append, BrowserTabs),
  130    send_list(BrowserTabs,
  131              [ append(new(xref_file_tree), files),
  132                append(new(xref_predicate_browser), predicates)
  133              ]),
  134    send_list(WSTabs,
  135              [ append(new(xref_depgraph), dependencies)
  136              ]),
  137    send(F, fill_toolbar, TD).
  138
  139tab_popup(_F, P:popup) :<-
  140    "Popup for tab labels"::
  141    new(P, popup),
  142    send_list(P, append,
  143              [ menu_item(close, message(@arg1, destroy)),
  144                menu_item(detach, message(@arg1, untab))
  145              ]).
  146
  147fill_toolbar(F, TD:tool_dialog) :->
  148    send(TD, append, new(File, popup(file))),
  149    send(TD, append,
  150         new(Settings, popup(settings,
  151                             message(F, setting, @arg1, @arg2)))),
  152    send(TD, append, new(View, popup(view))),
  153    send(TD, append, new(Help, popup(help))),
  154    send_list(File, append,
  155              [ menu_item(exit, message(F, destroy))
  156              ]),
  157    send_list(View, append,
  158              [ menu_item(refresh, message(F, update))
  159              ]),
  160    send_list(Help, append,
  161              [ menu_item(about, message(F, about))
  162              ]),
  163    send(Settings, show_current, @on),
  164    send(Settings, multiple_selection, @on),
  165    send(F, update_setting_menu).
  166
  167about(_F) :->
  168    version(Version),
  169    send(@display, inform,
  170         string('SWI-Prolog cross-referencer version %s\n\c
  171                    By Jan Wielemaker', Version)).
  172
  173:- pce_group(parts).
  174
  175workspace(F, Which:name, Create:[bool], Expose:bool, WS:window) :<-
  176    "Find named workspace"::
  177    get(F, member, workspaces, Tabs),
  178    (   get(Tabs, member, Which, WS)
  179    ->  true
  180    ;   Create == @on
  181    ->  workspace_term(Which, New),
  182        new(WS, New),
  183        send(WS, name, Which),
  184        send(Tabs, append, WS)
  185    ),
  186    (   Expose == @on
  187    ->  send(Tabs, on_top, WS?name)
  188    ;   true
  189    ).
  190
  191workspace_term(file_info, prolog_file_info).
  192workspace_term(header,    xref_view).
  193
  194browser(F, Which:name, Browser:browser) :<-
  195    "Find named browser"::
  196    get(F, member, browsers, Tabs),
  197    get(Tabs, member, Which, Browser).
  198
  199update(F) :->
  200    "Update all windows"::
  201    send(F, xref_all),
  202    get(F, member, browsers, Tabs),
  203    send(Tabs?members, for_some,
  204         message(@arg1, update)),
  205    get(F, member, workspaces, WSs),
  206    send(WSs?members, for_some,
  207         message(@arg1, update)).
  208
  209xref_all(F) :->
  210    "Run X-referencer on all files"::
  211    forall(source_file(File),
  212           send(F, xref_file, File)).
  213
  214xref_file(F, File:name) :->
  215    "XREF a single file if not already done"::
  216    (   xref_done(File, Time),
  217        catch(time_file(File, Modified), _, fail),
  218        Modified == Time
  219    ->  true
  220    ;   send(F, report, progress, 'XREF %s', File),
  221        xref_source(File, [silent(true)]),
  222        send(F, report, done)
  223    ).
  224
  225:- pce_group(actions).
  226
  227
  228file_info(F, File:name) :->
  229    "Show summary info on File"::
  230    get(F, workspace, file_info, @on, @on, Window),
  231    send(Window, file, File),
  232    broadcast(xref_refresh_file(File)).
  233
  234file_header(F, File:name) :->
  235    "Create import/export header"::
  236    get(F, workspace, header, @on, @on, View),
  237    send(View, file_header, File).
  238
  239:- pce_group(settings).
  240
  241update_setting_menu(F) :->
  242    "Update the menu for the settings with the current values"::
  243    get(F, member, tool_dialog, TD),
  244    get(TD, member, menu_bar, MB),
  245    get(MB, member, settings, Popup),
  246    send(Popup, clear),
  247    setting_menu(Entries),
  248    (   member(Name, Entries),
  249        setting(Name, Value),
  250        send(Popup, append, new(MI, menu_item(Name))),
  251        (   Value == true
  252        ->  send(MI, selected, @on)
  253        ;   true
  254        ),
  255        fail ; true
  256    ).
  257
  258setting(F, S:name, PceVal:bool) :->
  259    "Update setting and redo analysis"::
  260    pce_to_prolog_bool(PceVal, Val),
  261    retractall(setting(S, _)),
  262    assert(setting(S, Val)),
  263    send(F, update).
  264
  265pce_to_prolog_bool(@on, true).
  266pce_to_prolog_bool(@off, false).
  267
  268:- pce_end_class(xref_frame).
  269
  270
  271                 /*******************************
  272                 *            WORKSPACE         *
  273                 *******************************/
  274
  275:- pce_begin_class(xref_depgraph, picture,
  276                   "Workspace showing dependecies").
  277:- use_class_template(arm).
  278:- use_class_template(print_graphics).
  279
  280initialise(W) :->
  281    send_super(W, initialise),
  282    send(W, popup, new(P, popup)),
  283    send_list(P, append,
  284              [ menu_item(layout, message(W, layout)),
  285                gap,
  286                menu_item(view_whole_project, message(W, show_project)),
  287                gap,
  288                menu_item(clear, message(W, clear, destroy)),
  289                gap,
  290                menu_item(print, message(W, print))
  291              ]).
  292
  293update(P) :->
  294    "Initial screen"::
  295    send(P, display,
  296         new(T, text('Drag files or directories to dependency view\n\c
  297                          or use background menu to show the whole project')),
  298         point(10,10)),
  299    send(T, name, intro_text),
  300    send(T, colour, grey50).
  301
  302remove_intro_text(P) :->
  303    "Remove the introductionary text"::
  304    (   get(P, member, intro_text, Text)
  305    ->  send(Text, destroy)
  306    ;   true
  307    ).
  308
  309show_project(P) :->
  310    get(P, sources, Sources),
  311    send(P, clear, destroy),
  312    forall(member(Src, Sources),
  313           send(P, append, Src)),
  314    send(P, update_links),
  315    send(P, layout).
  316
  317sources(_, Sources:prolog) :<-
  318    findall(S, dep_source(S), Sources).
  319
  320%!  dep_source(?Src)
  321%
  322%   Generate all sources for the dependecy graph one-by-one.
  323
  324dep_source(Src) :-
  325    source_file(Src),
  326    (   setting(hide_system_files, true)
  327    ->  \+ library_file(Src)
  328    ;   true
  329    ),
  330    (   setting(hide_profile_files, true)
  331    ->  \+ profile_file(Src)
  332    ;   true
  333    ).
  334
  335append(P, File:name, Create:[bool|{always}]) :->
  336    "Append File.  If Create == always also if a system file"::
  337    default(Create, @on, C),
  338    get(P, node, File, C, _).
  339
  340node(G, File:name, Create:[bool|{always}], Pos:[point],
  341     Gr:xref_file_graph_node) :<-
  342    "Get the node representing File"::
  343    (   get(G, member, File, Gr)
  344    ->  true
  345    ;   (   Create == @on
  346        ->  dep_source(File)
  347        ;   Create == always
  348        ),
  349        (   Pos == @default
  350        ->  get(G?visible, center, At)
  351        ;   At = Pos
  352        ),
  353        send(G, display, new(Gr, xref_file_graph_node(File)), At),
  354        send(G, remove_intro_text)
  355    ).
  356
  357update_links(G) :->
  358    "Add all export links"::
  359    send(G?graphicals, for_all,
  360         if(message(@arg1, instance_of, xref_file_graph_node),
  361            message(@arg1, create_export_links))).
  362
  363layout(G, MoveOnly:[chain]) :->
  364    "Do graph layout"::
  365    get(G?graphicals, find_all,
  366        message(@arg1, instance_of, xref_file_graph_node), Nodes),
  367    get(Nodes, find_all, not(@arg1?connections), UnConnected),
  368    send(Nodes, subtract, UnConnected),
  369    new(Pos, point(10,10)),
  370    send(UnConnected, for_all,
  371         and(message(@arg1, position, Pos),
  372             message(Pos, offset, 0, 25))),
  373    get(Nodes, head, First),
  374    send(First, layout,
  375         nominal := 100,
  376         iterations := 1000,
  377         network := Nodes,
  378         move_only := MoveOnly).
  379
  380
  381:- pce_group(dragdrop).
  382
  383drop(G, Obj:object, Pos:point) :->
  384    "Drop a file on the graph"::
  385    (   send(Obj, instance_of, xref_file_text)
  386    ->  get(Obj, path, File),
  387        (   get(G, node, File, Node)
  388        ->  send(Node, flash)
  389        ;   get(G, node, File, always, Pos, _Node),
  390            send(G, update_links)
  391        )
  392    ;   send(Obj, instance_of, xref_directory_text)
  393    ->  get(Obj, files, Files),
  394        layout_new(G,
  395                   (   send(Files, for_all,
  396                            message(G, append, @arg1, always)),
  397                       send(G, update_links)
  398                   ))
  399    ).
  400
  401preview_drop(G, Obj:object*, Pos:point) :->
  402    "Show preview of drop"::
  403    (   Obj == @nil
  404    ->  send(G, report, status, '')
  405    ;   send(Obj, instance_of, xref_file_text)
  406    ->  (   get(Obj, device, G)
  407        ->  send(Obj, move, Pos)
  408        ;   get(Obj, path, File),
  409            get(Obj, string, Label),
  410            (   get(G, node, File, _Node)
  411            ->  send(G, report, status, '%s: already in graph', Label)
  412            ;   send(G, report, status, 'Add %s to graph', Label)
  413            )
  414        )
  415    ;   send(Obj, instance_of, xref_directory_text)
  416    ->  get(Obj, path, Path),
  417        send(G, report, status, 'Add files from directory %s', Path)
  418    ).
  419
  420:- pce_end_class(xref_depgraph).
  421
  422:- pce_begin_class(xref_file_graph_node, xref_file_text).
  423
  424:- send(@class, handle, handle(w/2, 0, link, north)).  425:- send(@class, handle, handle(w, h/2, link, west)).  426:- send(@class, handle, handle(w/2, h, link, south)).  427:- send(@class, handle, handle(0, h/2, link, east)).  428
  429initialise(N, File:name) :->
  430    send_super(N, initialise, File),
  431    send(N, font, bold),
  432    send(N, background, grey80).
  433
  434create_export_links(N, Add:[bool]) :->
  435    "Create the export links to other files"::
  436    get(N, path, Exporter),
  437    forall(export_link(Exporter, Importer, Callables),
  438           create_export_link(N, Add, Importer, Callables)).
  439
  440create_export_link(From, Add, Importer, Callables) :-
  441    (   get(From?device, node, Importer, Add, INode)
  442    ->  send(From, link, INode, Callables)
  443    ;   true
  444    ).
  445
  446create_import_links(N, Add:[bool]) :->
  447    "Create the import links from other files"::
  448    get(N, path, Importer),
  449    forall(export_link(Exporter, Importer, Callables),
  450           create_import_link(N, Add, Exporter, Callables)).
  451
  452create_import_link(From, Add, Importer, Callables) :-
  453    (   get(From?device, node, Importer, Add, INode)
  454    ->  send(INode, link, From, Callables)
  455    ;   true
  456    ).
  457
  458link(N, INode:xref_file_graph_node, Callables:prolog) :->
  459    "Create export link to INode"::
  460    (   get(N, connections, INode, CList),
  461        get(CList, find, @arg1?from == N, C)
  462    ->  send(C, callables, Callables)
  463    ;   new(L, xref_export_connection(N, INode, Callables)),
  464        send(L, hide)
  465    ).
  466
  467:- pce_global(@xref_file_graph_node_recogniser,
  468              make_xref_file_graph_node_recogniser).  469
  470make_xref_file_graph_node_recogniser(G) :-
  471    new(G, move_gesture(left, '')).
  472
  473event(N, Ev:event) :->
  474    "Add moving (overrule supreclass"::
  475    (   send(@xref_file_graph_node_recogniser, event, Ev)
  476    ->  true
  477    ;   send_super(N, event, Ev)
  478    ).
  479
  480popup(N, Popup:popup) :<-
  481    get_super(N, popup, Popup),
  482    send_list(Popup, append,
  483              [ gap,
  484                menu_item(show_exports,
  485                          message(@arg1, show_import_exports, export)),
  486                menu_item(show_imports,
  487                          message(@arg1, show_import_exports, import)),
  488                gap,
  489                menu_item(hide,
  490                          message(@arg1, destroy))
  491              ]).
  492
  493show_import_exports(N, Which:{import,export}) :->
  494    "Show who I'm exporting to"::
  495    get(N, device, G),
  496    layout_new(G,
  497               (   (   Which == export
  498                   ->  send(N, create_export_links, @on)
  499                   ;   send(N, create_import_links, @on)
  500                   ),
  501                   send(G, update_links)
  502               )).
  503
  504layout_new(G, Goal) :-
  505    get(G?graphicals, find_all,
  506        message(@arg1, instance_of, xref_file_graph_node), Nodes0),
  507    Goal,
  508    get(G?graphicals, find_all,
  509        message(@arg1, instance_of, xref_file_graph_node), Nodes),
  510    send(Nodes, subtract, Nodes0),
  511    (   send(Nodes, empty)
  512    ->  send(G, report, status, 'No nodes added')
  513    ;   send(G, layout, Nodes),
  514        get(Nodes, size, Size),
  515        send(G, report, status, '%d nodes added', Size)
  516    ).
  517
  518:- pce_end_class(xref_file_graph_node).
  519
  520:- pce_begin_class(xref_export_connection, tagged_connection).
  521
  522variable(callables, prolog, get, "Callables in Import/export link").
  523
  524initialise(C, From:xref_file_graph_node, To:xref_file_graph_node,
  525           Callables:prolog) :->
  526    send_super(C, initialise, From, To),
  527    send(C, arrows, second),
  528    send(C, slot, callables, Callables),
  529    length(Callables, N),
  530    send(C, tag, xref_export_connection_tag(C, N)).
  531
  532callables(C, Callables:prolog) :->
  533    send(C, slot, callables, Callables). % TBD: update tag?
  534
  535called_by_popup(Conn, P:popup) :<-
  536    "Create popup to show relating predicates"::
  537    new(P, popup(called_by, message(Conn, edit_callable, @arg1))),
  538    get(Conn, callables, Callables),
  539    get(Conn?from, path, ExportFile),
  540    get(Conn?to, path, ImportFile),
  541    sort_callables(Callables, Sorted),
  542    forall(member(C, Sorted),
  543           append_io_callable(P, ImportFile, ExportFile, C)).
  544
  545%!  append_io_callable(+Popup, -ImportFile, +Callable)
  546
  547append_io_callable(P, ImportFile, ExportFile, Callable) :-
  548    callable_to_label(Callable, Label),
  549    send(P, append, new(MI, menu_item(@nil, @default, Label))),
  550    send(MI, popup, new(P2, popup)),
  551    send(P2, append,
  552         menu_item(prolog('<definition>'(Callable)),
  553                   @default, definition?label_name)),
  554    send(P2, append, gap),
  555    qualify_from_file(Callable, ExportFile, QCall),
  556    findall(By, used_in(ImportFile, QCall, By), ByList0),
  557    sort_callables(ByList0, ByList),
  558    forall(member(C, ByList),
  559           ( callable_to_label(C, CLabel),
  560             send(P2, append, menu_item(prolog(C), @default, CLabel)))).
  561
  562edit_callable(C, Callable:prolog) :->
  563    "Edit definition or callers"::
  564    (   Callable = '<definition>'(Def)
  565    ->  get(C?from, path, ExportFile),
  566        edit_callable(Def, ExportFile)
  567    ;   get(C?to, path, ImportFile),
  568        edit_callable(Callable, ImportFile)
  569    ).
  570
  571:- pce_end_class(xref_export_connection).
  572
  573
  574:- pce_begin_class(xref_export_connection_tag, text,
  575                   "Text showing import/export count").
  576
  577variable(connection, xref_export_connection, get, "Related connection").
  578
  579initialise(Tag, C:xref_export_connection, N:int) :->
  580    send(Tag, slot, connection, C),
  581    send_super(Tag, initialise, string('(%d)', N)),
  582    send(Tag, colour, blue),
  583    send(Tag, underline, @on).
  584
  585:- pce_global(@xref_export_connection_tag_recogniser,
  586              new(popup_gesture(@receiver?connection?called_by_popup, left))).
  587
  588event(Tag, Ev:event) :->
  589    (   send_super(Tag, event, Ev)
  590    ->  true
  591    ;   send(@xref_export_connection_tag_recogniser, event, Ev)
  592    ).
  593
  594:- pce_end_class(xref_export_connection_tag).
  595
  596
  597
  598%!  export_link(+ExportingFile, -ImportingFile, -Callables) is det.
  599%!  export_link(-ExportingFile, +ImportingFile, -Callables) is det.
  600%
  601%   Callables are exported from ExportingFile to ImportingFile.
  602
  603export_link(ExportFile, ImportingFile, Callables) :-
  604    setof(Callable,
  605          export_link_1(ExportFile, ImportingFile, Callable),
  606          Callables0),
  607    sort_callables(Callables0, Callables).
  608
  609
  610export_link_1(ExportFile, ImportFile, Callable) :-       % module export
  611    nonvar(ExportFile),
  612    xref_module(ExportFile, Module),
  613    !,
  614    (   xref_exported(ExportFile, Callable),
  615        xref_defined(ImportFile, Callable, imported(ExportFile)),
  616        xref_called(ImportFile, Callable)
  617    ;   defined(ExportFile, Callable),
  618        single_qualify(Module:Callable, QCall),
  619        xref_called(ImportFile, QCall)
  620    ),
  621    ImportFile \== ExportFile,
  622    atom(ImportFile).
  623export_link_1(ExportFile, ImportFile, Callable) :-      % Non-module export
  624    nonvar(ExportFile),
  625    !,
  626    defined(ExportFile, Callable),
  627    xref_called(ImportFile, Callable),
  628    atom(ImportFile),
  629    ExportFile \== ImportFile.
  630export_link_1(ExportFile, ImportFile, Callable) :-      % module import
  631    nonvar(ImportFile),
  632    xref_module(ImportFile, Module),
  633    !,
  634    xref_called(ImportFile, Callable),
  635    (   xref_defined(ImportFile, Callable, imported(ExportFile))
  636    ;   single_qualify(Module:Callable, QCall),
  637        QCall = M:G,
  638        (   defined(ExportFile, G),
  639            xref_module(ExportFile, M)
  640        ;   defined(ExportFile, QCall)
  641        )
  642    ),
  643    ImportFile \== ExportFile,
  644    atom(ExportFile).
  645export_link_1(ExportFile, ImportFile, Callable) :-      % Non-module import
  646    xref_called(ImportFile, Callable),
  647    \+ (  xref_defined(ImportFile, Callable, How),
  648          How \= imported(_)
  649       ),
  650                                    % see also undefined/2
  651    (   xref_defined(ImportFile, Callable, imported(ExportFile))
  652    ;   defined(ExportFile, Callable),
  653        \+ xref_module(ExportFile, _)
  654    ;   Callable = _:_,
  655        defined(ExportFile, Callable)
  656    ;   Callable = M:G,
  657        defined(ExportFile, G),
  658        xref_module(ExportFile, M)
  659    ).
  660
  661
  662                 /*******************************
  663                 *             FILTER           *
  664                 *******************************/
  665
  666:- pce_begin_class(xref_filter_dialog, dialog,
  667                   "Show filter options").
  668
  669class_variable(border, size, size(0,0)).
  670
  671initialise(D) :->
  672    send_super(D, initialise),
  673    send(D, hor_stretch, 100),
  674    send(D, hor_shrink, 100),
  675    send(D, name, filter_dialog),
  676    send(D, append, xref_file_filter_item(filter_on_filename)).
  677
  678resize(D) :->
  679    send(D, layout, D?visible?size).
  680
  681:- pce_end_class(xref_filter_dialog).
  682
  683
  684:- pce_begin_class(xref_file_filter_item, text_item,
  685                   "Filter files as you type").
  686
  687typed(FFI, Id) :->
  688    "Activate filter"::
  689    send_super(FFI, typed, Id),
  690    get(FFI, displayed_value, Current),
  691    get(FFI?frame, browser, files, Tree),
  692    (   send(Current, equal, '')
  693    ->  send(Tree, filter_file_name, @nil)
  694    ;   (   text_to_regex(Current, Filter)
  695        ->  send(Tree, filter_file_name, Filter)
  696        ;   send(FFI, report, status, 'Incomplete expression')
  697        )
  698    ).
  699
  700%!  text_to_regex(+Pattern, -Regex) is semidet.
  701%
  702%   Convert text to a regular expression.  Fail if the text
  703%   does not represent a valid regular expression.
  704
  705text_to_regex(Pattern, Regex) :-
  706    send(@pce, last_error, @nil),
  707    new(Regex, regex(Pattern)),
  708    ignore(pce_catch_error(_, send(Regex, search, ''))),
  709    get(@pce, last_error, @nil).
  710
  711:- pce_end_class(xref_file_filter_item).
  712
  713
  714
  715                 /*******************************
  716                 *           FILE TREE          *
  717                 *******************************/
  718
  719:- pce_begin_class(xref_file_tree, toc_window,
  720                   "Show loaded files as a tree").
  721:- use_class_template(arm).
  722
  723initialise(Tree) :->
  724    send_super(Tree, initialise),
  725    send(Tree, clear),
  726    listen(Tree, xref_refresh_file(File),
  727           send(Tree, refresh_file, File)).
  728
  729unlink(Tree) :->
  730    unlisten(Tree),
  731    send_super(Tree, unlink).
  732
  733refresh_file(Tree, File:name) :->
  734    "Update given file"::
  735    (   get(Tree, node, File, Node)
  736    ->  send(Node, set_flags)
  737    ;   true
  738    ).
  739
  740collapse_node(_, _:any) :->
  741    true.
  742
  743expand_node(_, _:any) :->
  744    true.
  745
  746update(FL) :->
  747    get(FL, expanded_ids, Chain),
  748    send(FL, clear),
  749    send(FL, report, progress, 'Building source tree ...'),
  750    send(FL, append_all_sourcefiles),
  751    send(FL, expand_ids, Chain),
  752    send(@display, synchronise),
  753    send(FL, report, progress, 'Flagging files ...'),
  754    send(FL, set_flags),
  755    send(FL, report, done).
  756
  757append_all_sourcefiles(FL) :->
  758    "Append all files loaded into Prolog"::
  759    forall(source_file(File),
  760           send(FL, append, File)),
  761    send(FL, sort).
  762
  763clear(Tree) :->
  764    "Remove all nodes, recreate the toplevel"::
  765    send_super(Tree, clear),
  766    send(Tree, root, new(Root, toc_folder(project, project))),
  767    forall(top_node(Name, Class),
  768           (   New =.. [Class, Name, Name],
  769               send(Tree, son, project, New))),
  770    send(Root, for_all, message(@arg1, collapsed, @off)).
  771
  772append(Tree, File:name) :->
  773    "Add Prolog source file"::
  774    send(Tree, append_node, new(prolog_file_node(File))).
  775
  776append_node(Tree, Node:toc_node) :->
  777    "Append a given node to the tree"::
  778    get(Node, parent_id, ParentId),
  779    (   get(Tree, node, ParentId, Parent)
  780    ->  true
  781    ;   send(Tree, append_node,
  782             new(Parent, prolog_directory_node(ParentId)))
  783    ),
  784    send(Parent, son, Node).
  785
  786sort(Tree) :->
  787    forall(top_node(Name, _),
  788           (   get(Tree, node, Name, Node),
  789               send(Node, sort_sons, ?(@arg1, compare, @arg2)),
  790               send(Node?sons, for_all, message(@arg1, sort))
  791           )).
  792
  793select_node(Tree, File:name) :->
  794    "User selected a node"::
  795    (   exists_file(File)
  796    ->  send(Tree?frame, file_info, File)
  797    ;   true
  798    ).
  799
  800set_flags(Tree) :->
  801    "Set alert-flags on all nodes"::
  802    forall(top_node(Name, _),
  803           (   get(Tree, node, Name, Node),
  804               (   send(Node, instance_of, prolog_directory_node)
  805               ->  send(Node, set_flags)
  806               ;   send(Node?sons, for_all, message(@arg1, set_flags))
  807               )
  808           )).
  809
  810top_node('.',           prolog_directory_node).
  811top_node('alias',       toc_folder).
  812top_node('/',           prolog_directory_node).
  813
  814
  815:- pce_group(filter).
  816
  817filter_file_name(Tree, Regex:regex*) :->
  818    "Only show files that match Regex"::
  819    (   Regex == @nil
  820    ->  send(Tree, filter_files, @nil)
  821    ;   send(Tree, filter_files,
  822             message(Regex, search, @arg1?base_name))
  823    ).
  824
  825filter_files(Tree, Filter:code*) :->
  826    "Highlight files that match Filter"::
  827    send(Tree, collapse_all),
  828    send(Tree, selection, @nil),
  829    (   Filter == @nil
  830    ->  send(Tree, expand_id, '.'),
  831        send(Tree, expand_id, project)
  832    ;   new(Count, number(0)),
  833        get(Tree?tree, root, Root),
  834        send(Root, for_all,
  835             if(and(message(@arg1, instance_of, prolog_file_node),
  836                    message(Filter, forward, @arg1)),
  837                and(message(Tree, show_node_path, @arg1),
  838                    message(Count, plus, 1)))),
  839        send(Tree, report, status, 'Filter on file name: %d hits', Count)
  840    ),
  841    send(Tree, scroll_to, point(0,0)).
  842
  843show_node_path(Tree, Node:node) :->
  844    "Select Node and make sure all parents are expanded"::
  845    send(Node, selected, @on),
  846    send(Tree, expand_parents, Node).
  847
  848expand_parents(Tree, Node:node) :->
  849    (   get(Node, collapsed, @nil)
  850    ->  true
  851    ;   send(Node, collapsed, @off)
  852    ),
  853    send(Node?parents, for_all, message(Tree, expand_parents, @arg1)).
  854
  855collapse_all(Tree) :->
  856    "Collapse all nodes"::
  857    get(Tree?tree, root, Root),
  858    send(Root, for_all,
  859         if(@arg1?collapsed == @off,
  860            message(@arg1, collapsed, @on))).
  861
  862:- pce_end_class(xref_file_tree).
  863
  864
  865:- pce_begin_class(prolog_directory_node, toc_folder,
  866                   "Represent a directory").
  867
  868variable(flags, name*, get, "Warning status").
  869
  870initialise(DN, Dir:name, Label:[name]) :->
  871    "Create a directory node"::
  872    (   Label \== @default
  873    ->  Name = Label
  874    ;   file_alias_path(Name, Dir)
  875    ->  true
  876    ;   file_base_name(Dir, Name)
  877    ),
  878    send_super(DN, initialise, xref_directory_text(Dir, Name), Dir).
  879
  880parent_id(FN, ParentId:name) :<-
  881    "Get id for the parent"::
  882    get(FN, identifier, Path),
  883    (   file_alias_path(_, Path)
  884    ->  ParentId = alias
  885    ;   file_directory_name(Path, ParentId)
  886    ).
  887
  888sort(DN) :->
  889    "Sort my sons"::
  890    send(DN, sort_sons, ?(@arg1, compare, @arg2)),
  891    send(DN?sons, for_all, message(@arg1, sort)).
  892
  893compare(DN, Node:toc_node, Diff:{smaller,equal,larger}) :<-
  894    "Compare for sorting children"::
  895    (   send(Node, instance_of, prolog_file_node)
  896    ->  Diff = smaller
  897    ;   get(DN, label, L1),
  898        get(Node, label, L2),
  899        get(L1, compare, L2, Diff)
  900    ).
  901
  902set_flags(DN) :->
  903    "Set alert images"::
  904    send(DN?sons, for_all, message(@arg1, set_flags)),
  905    (   get(DN?sons, find, @arg1?flags \== ok, _Node)
  906    ->  send(DN, collapsed_image, @xref_alert_closedir),
  907        send(DN, expanded_image, @xref_alert_opendir),
  908        send(DN, slot, flags, alert)
  909    ;   send(DN, collapsed_image, @xref_ok_closedir),
  910        send(DN, expanded_image, @xref_ok_opendir),
  911        send(DN, slot, flags, ok)
  912    ),
  913    send(@display, synchronise).
  914
  915:- pce_end_class(prolog_directory_node).
  916
  917
  918:- pce_begin_class(prolog_file_node, toc_file,
  919                   "Represent a file").
  920
  921variable(flags,         name*, get, "Warning status").
  922variable(base_name,     name,  get, "Base-name of file").
  923
  924initialise(FN, File:name) :->
  925    "Create from a file"::
  926    absolute_file_name(File, Path),
  927    send_super(FN, initialise, new(T, xref_file_text(Path)), Path),
  928    file_base_name(File, Base),
  929    send(FN, slot, base_name, Base),
  930    send(T, default_action, info).
  931
  932basename(FN, BaseName:name) :<-
  933    "Get basename of the file for sorting"::
  934    get(FN, identifier, File),
  935    file_base_name(File, BaseName).
  936
  937parent_id(FN, ParentId:name) :<-
  938    "Get id for the parent"::
  939    get(FN, identifier, Path),
  940    file_directory_name(Path, Dir),
  941    (   file_alias_path('.', Dir)
  942    ->  ParentId = '.'
  943    ;   ParentId = Dir
  944    ).
  945
  946sort(_) :->
  947    true.
  948
  949compare(FN, Node:toc_node, Diff:{smaller,equal,larger}) :<-
  950    "Compare for sorting children"::
  951    (   send(Node, instance_of, prolog_directory_node)
  952    ->  Diff = larger
  953    ;   get(FN, basename, L1),
  954        get(Node, basename, L2),
  955        get(L1, compare, L2, Diff)
  956    ).
  957
  958set_flags(FN) :->
  959    "Set alert images"::
  960    get(FN, identifier, File),
  961    (   file_warnings(File, _)
  962    ->  send(FN, image, @xref_alert_file),
  963        send(FN, slot, flags, alert)
  964    ;   send(FN, image, @xref_ok_file),
  965        send(FN, slot, flags, ok)
  966    ),
  967    send(@display, synchronise).
  968
  969:- pce_global(@xref_ok_file,
  970              make_xref_image([ image('16x16/doc.xpm'),
  971                                image('16x16/ok.xpm')
  972                              ])).  973:- pce_global(@xref_alert_file,
  974              make_xref_image([ image('16x16/doc.xpm'),
  975                                image('16x16/alert.xpm')
  976                              ])).  977
  978:- pce_global(@xref_ok_opendir,
  979              make_xref_image([ image('16x16/opendir.xpm'),
  980                                image('16x16/ok.xpm')
  981                              ])).  982:- pce_global(@xref_alert_opendir,
  983              make_xref_image([ image('16x16/opendir.xpm'),
  984                                image('16x16/alert.xpm')
  985                              ])).  986
  987:- pce_global(@xref_ok_closedir,
  988              make_xref_image([ image('16x16/closedir.xpm'),
  989                                image('16x16/ok.xpm')
  990                              ])).  991:- pce_global(@xref_alert_closedir,
  992              make_xref_image([ image('16x16/closedir.xpm'),
  993                                image('16x16/alert.xpm')
  994                              ])).  995
  996make_xref_image([First|More], Image) :-
  997    new(Image, image(@nil, 0, 0, pixmap)),
  998    send(Image, copy, First),
  999    forall(member(I2, More),
 1000           send(Image, draw_in, bitmap(I2))).
 1001
 1002:- pce_end_class(prolog_file_node).
 1003
 1004
 1005
 1006
 1007                 /*******************************
 1008                 *           FILE INFO          *
 1009                 *******************************/
 1010
 1011
 1012:- pce_begin_class(prolog_file_info, window,
 1013                   "Show information on File").
 1014:- use_class_template(arm).
 1015
 1016variable(tabular,     tabular, get, "Displayed table").
 1017variable(prolog_file, name*,   get, "Displayed Prolog file").
 1018
 1019initialise(W, File:[name]*) :->
 1020    send_super(W, initialise),
 1021    send(W, pen, 0),
 1022    send(W, scrollbars, vertical),
 1023    send(W, display, new(T, tabular)),
 1024    send(T, rules, all),
 1025    send(T, cell_spacing, -1),
 1026    send(W, slot, tabular, T),
 1027    (   atom(File)
 1028    ->  send(W, prolog_file, File)
 1029    ;   true
 1030    ).
 1031
 1032resize(W) :->
 1033    send_super(W, resize),
 1034    get(W?visible, width, Width),
 1035    send(W?tabular, table_width, Width-3).
 1036
 1037
 1038file(V, File0:name*) :->
 1039    "Set vizualized file"::
 1040    (   File0 == @nil
 1041    ->  File = File0
 1042    ;   absolute_file_name(File0, File)
 1043    ),
 1044    (   get(V, prolog_file, File)
 1045    ->  true
 1046    ;   send(V, slot, prolog_file, File),
 1047        send(V, update)
 1048    ).
 1049
 1050
 1051clear(W) :->
 1052    send(W?tabular, clear).
 1053
 1054
 1055update(V) :->
 1056    "Show information on the current file"::
 1057    send(V, clear),
 1058    send(V, scroll_to, point(0,0)),
 1059    (   get(V, prolog_file, File),
 1060        File \== @nil
 1061    ->  send(V?frame, xref_file, File), % Make sure data is up-to-date
 1062        send(V, show_info)
 1063    ;   true
 1064    ).
 1065
 1066
 1067module(W, Module:name) :<-
 1068    "Module associated with this file"::
 1069    get(W, prolog_file, File),
 1070    (   xref_module(File, Module)
 1071    ->  true
 1072    ;   Module = user               % TBD: does not need to be true!
 1073    ).
 1074
 1075:- pce_group(info).
 1076
 1077show_info(W) :->
 1078    get(W, tabular, T),
 1079    BG = (background := khaki1),
 1080    get(W, prolog_file, File),
 1081    new(FG, xref_file_text(File)),
 1082    send(FG, font, huge),
 1083    send(T, append, FG, halign := center, colspan := 2, BG),
 1084    send(T, next_row),
 1085    send(W, show_module),
 1086    send(W, show_modified),
 1087    send(W, show_undefined),
 1088    send(W, show_not_called),
 1089    send(W, show_exports),
 1090    send(W, show_imports),
 1091    true.
 1092
 1093show_module(W) :->
 1094    "Show basic module info"::
 1095    get(W, prolog_file, File),
 1096    get(W, tabular, T),
 1097    (   xref_module(File, Module)
 1098    ->  send(T, append, 'Module:', bold, right),
 1099        send(T, append, Module),
 1100        send(T, next_row)
 1101    ;   true
 1102    ).
 1103
 1104show_modified(W) :->
 1105    get(W, prolog_file, File),
 1106    get(W, tabular, T),
 1107    time_file(File, Stamp),
 1108    format_time(string(Modified), '%+', Stamp),
 1109    send(T, append, 'Modified:', bold, right),
 1110    send(T, append, Modified),
 1111    send(T, next_row).
 1112
 1113show_exports(W) :->
 1114    get(W, prolog_file, File),
 1115    (   xref_module(File, Module),
 1116        findall(E, xref_exported(File, E), Exports),
 1117        Exports \== []
 1118    ->  send(W, show_export_header, export, imported_by),
 1119        sort_callables(Exports, Sorted),
 1120        forall(member(Callable, Sorted),
 1121               send(W, show_module_export, File, Module, Callable))
 1122    ;   true
 1123    ),
 1124    (   findall(C-Fs,
 1125                ( setof(F, export_link_1(File, F, C), Fs),
 1126                  \+ xref_exported(File, C)),
 1127                Pairs0),
 1128        Pairs0 \== []
 1129    ->  send(W, show_export_header, defined, used_by),
 1130        keysort(Pairs0, Pairs),     % TBD
 1131        forall(member(Callable-ImportFiles, Pairs),
 1132               send(W, show_file_export, Callable, ImportFiles))
 1133    ;   true
 1134    ).
 1135
 1136show_export_header(W, Left:name, Right:name) :->
 1137    get(W, tabular, T),
 1138    BG = (background := khaki1),
 1139    send(T, append, Left?label_name, bold, center, BG),
 1140    send(T, append, Right?label_name, bold, center, BG),
 1141    send(T, next_row).
 1142
 1143show_module_export(W, File:name, Module:name, Callable:prolog) :->
 1144    get(W, prolog_file, File),
 1145    get(W, tabular, T),
 1146    send(T, append, xref_predicate_text(Module:Callable, @default, File)),
 1147    findall(In, exported_to(File, Callable, In), InL),
 1148    send(T, append, new(XL, xref_graphical_list)),
 1149    (   InL == []
 1150    ->  true
 1151    ;   sort_files(InL, Sorted),
 1152        forall(member(F, Sorted),
 1153               send(XL, append, xref_imported_by(F, Callable)))
 1154    ),
 1155    send(T, next_row).
 1156
 1157show_file_export(W, Callable:prolog, ImportFiles:prolog) :->
 1158    get(W, prolog_file, File),
 1159    get(W, tabular, T),
 1160    send(T, append, xref_predicate_text(Callable, @default, File)),
 1161    send(T, append, new(XL, xref_graphical_list)),
 1162    sort_files(ImportFiles, Sorted),
 1163    qualify_from_file(Callable, File, QCall),
 1164    forall(member(F, Sorted),
 1165           send(XL, append, xref_imported_by(F, QCall))),
 1166    send(T, next_row).
 1167
 1168qualify_from_file(Callable, _, Callable) :-
 1169    Callable = _:_,
 1170    !.
 1171qualify_from_file(Callable, File, M:Callable) :-
 1172    xref_module(File, M),
 1173    !.
 1174qualify_from_file(Callable, _, Callable).
 1175
 1176
 1177%!  exported_to(+ExportFile, +Callable, -ImportFile)
 1178%
 1179%   ImportFile imports Callable from ExportFile.  The second clause
 1180%   deals with auto-import.
 1181%
 1182%   TBD: Make sure the autoload library is loaded before we begin.
 1183
 1184exported_to(ExportFile, Callable, ImportFile) :-
 1185    xref_defined(ImportFile, Callable, imported(ExportFile)),
 1186    atom(ImportFile).               % avoid XPCE buffers.
 1187exported_to(ExportFile, Callable, ImportFile) :-
 1188    '$autoload':library_index(Callable, _, ExportFileNoExt),
 1189    file_name_extension(ExportFileNoExt, _, ExportFile),
 1190    xref_called(ImportFile, Callable),
 1191    atom(ImportFile),
 1192    \+ xref_defined(ImportFile, Callable, _).
 1193
 1194show_imports(W) :->
 1195    "Show predicates we import"::
 1196    get(W, prolog_file, File),
 1197    findall(E-Cs,
 1198            setof(C, export_link_1(E, File, C), Cs),
 1199            Pairs),
 1200    (   Pairs \== []
 1201    ->  sort(Pairs, Sorted),        % TBD: use sort_files/2
 1202        (   xref_module(File, _)
 1203        ->  send(W, show_export_header, from, imports)
 1204        ;   send(W, show_export_header, from, uses)
 1205        ),
 1206        forall(member(E-Cs, Sorted),
 1207               send(W, show_import, E, Cs))
 1208    ;   true
 1209    ).
 1210
 1211show_import(W, File:name, Callables:prolog) :->
 1212    "Show imports from file"::
 1213    get(W, tabular, T),
 1214    send(T, append, xref_file_text(File)),
 1215    send(T, append, new(XL, xref_graphical_list)),
 1216    sort_callables(Callables, Sorted),
 1217    forall(member(C, Sorted),
 1218           send(XL, append, xref_predicate_text(C, @default, File))),
 1219    send(T, next_row).
 1220
 1221
 1222show_undefined(W) :->
 1223    "Add underfined predicates to table"::
 1224    get(W, prolog_file, File),
 1225    findall(Undef, undefined(File, Undef), UndefList),
 1226    (   UndefList == []
 1227    ->  true
 1228    ;   BG = (background := khaki1),
 1229        get(W, tabular, T),
 1230        (   setting(warn_autoload, true)
 1231        ->  Label = 'Undefined/autoload'
 1232        ;   Label = 'Undefined'
 1233        ),
 1234        send(T, append, Label, bold, center, BG),
 1235        send(T, append, 'Called by', bold, center, BG),
 1236        send(T, next_row),
 1237        sort_callables(UndefList, Sorted),
 1238        forall(member(Callable, Sorted),
 1239               send(W, show_undef, Callable))
 1240    ).
 1241
 1242show_undef(W, Callable:prolog) :->
 1243    "Show undefined predicate"::
 1244    get(W, prolog_file, File),
 1245    get(W, module, Module),
 1246    get(W, tabular, T),
 1247    send(T, append,
 1248         xref_predicate_text(Module:Callable, undefined, File)),
 1249    send(T, append, new(L, xref_graphical_list)),
 1250    findall(By, xref_called(File, Callable, By), By),
 1251    sort_callables(By, Sorted),
 1252    forall(member(P, Sorted),
 1253           send(L, append, xref_predicate_text(Module:P, called_by, File))),
 1254    send(T, next_row).
 1255
 1256
 1257show_not_called(W) :->
 1258    "Show predicates that are not called"::
 1259    get(W, prolog_file, File),
 1260    findall(NotCalled, not_called(File, NotCalled), NotCalledList),
 1261    (   NotCalledList == []
 1262    ->  true
 1263    ;   BG = (background := khaki1),
 1264        get(W, tabular, T),
 1265        send(T, append, 'Not called', bold, center, colspan := 2, BG),
 1266         send(T, next_row),
 1267        sort_callables(NotCalledList, Sorted),
 1268        forall(member(Callable, Sorted),
 1269               send(W, show_not_called_pred, Callable))
 1270    ).
 1271
 1272show_not_called_pred(W, Callable:prolog) :->
 1273    "Show a not-called predicate"::
 1274    get(W, prolog_file, File),
 1275    get(W, module, Module),
 1276    get(W, tabular, T),
 1277    send(T, append,
 1278         xref_predicate_text(Module:Callable, not_called, File),
 1279         colspan := 2),
 1280    send(T, next_row).
 1281
 1282:- pce_end_class(prolog_file_info).
 1283
 1284
 1285:- pce_begin_class(xref_predicate_text, text,
 1286                   "Text representing a predicate").
 1287
 1288class_variable(colour, colour, dark_green).
 1289
 1290variable(callable,       prolog, get, "Predicate indicator").
 1291variable(classification, [name], get, "Classification of the predicate").
 1292variable(file,           name*,  get, "File of predicate").
 1293
 1294initialise(T, Callable0:prolog,
 1295           Class:[{undefined,called_by,not_called}],
 1296           File:[name]) :->
 1297    "Create from callable or predicate indicator"::
 1298    single_qualify(Callable0, Callable),
 1299    send(T, slot, callable, Callable),
 1300    callable_to_label(Callable, File, Label),
 1301    send_super(T, initialise, Label),
 1302    (   File \== @default
 1303    ->  send(T, slot, file, File)
 1304    ;   true
 1305    ),
 1306    send(T, classification, Class).
 1307
 1308%!  single_qualify(+Term, -Qualified)
 1309%
 1310%   Strip redundant M: from the term, leaving at most one qualifier.
 1311
 1312single_qualify(_:Q0, Q) :-
 1313    is_qualified(Q0),
 1314    !,
 1315    single_qualify(Q0, Q).
 1316single_qualify(Q, Q).
 1317
 1318is_qualified(M:_) :-
 1319    atom(M).
 1320
 1321pi(IT, PI:prolog) :<-
 1322    "Get predicate as predicate indicator (Name/Arity)"::
 1323    get(IT, callable, Callable),
 1324    to_predicate_indicator(Callable, PI).
 1325
 1326classification(T, Class:[name]) :->
 1327    send(T, slot, classification, Class),
 1328    (   Class == undefined
 1329    ->  get(T, callable, Callable),
 1330        strip_module(Callable, _, Plain),
 1331        (   autoload_predicate(Plain)
 1332        ->  send(T, colour, navy_blue),
 1333            send(T, slot, classification, autoload)
 1334        ;   global_predicate(Plain)
 1335        ->  send(T, colour, navy_blue),
 1336            send(T, slot, classification, global)
 1337        ;   send(T, colour, red)
 1338        )
 1339    ;   Class == not_called
 1340    ->  send(T, colour, red)
 1341    ;   true
 1342    ).
 1343
 1344:- pce_global(@xref_predicate_text_recogniser,
 1345              new(handler_group(@arm_recogniser,
 1346                                click_gesture(left, '', single,
 1347                                              message(@receiver, edit))))).
 1348
 1349event(T, Ev:event) :->
 1350    (   send_super(T, event, Ev)
 1351    ->  true
 1352    ;   send(@xref_predicate_text_recogniser, event, Ev)
 1353    ).
 1354
 1355
 1356arm(TF, Val:bool) :->
 1357    "Preview activiity"::
 1358    (   Val == @on
 1359    ->  send(TF, underline, @on),
 1360        (   get(TF, classification, Class),
 1361            Class \== @default
 1362        ->  send(TF, report, status,
 1363                 '%s predicate %s', Class?capitalise, TF?string)
 1364        ;   send(TF, report, status,
 1365                 'Predicate %s', TF?string)
 1366        )
 1367    ;   send(TF, underline, @off),
 1368        send(TF, report, status, '')
 1369    ).
 1370
 1371edit(T) :->
 1372    get(T, file, File),
 1373    get(T, callable, Callable),
 1374    edit_callable(Callable, File).
 1375
 1376:- pce_end_class(xref_predicate_text).
 1377
 1378
 1379:- pce_begin_class(xref_file_text, text,
 1380                   "Represent a file-name").
 1381
 1382variable(path,           name,         get, "Filename represented").
 1383variable(default_action, name := edit, both, "Default on click").
 1384
 1385initialise(TF, File:name) :->
 1386    absolute_file_name(File, Path),
 1387    file_name_on_path(Path, ShortId),
 1388    short_file_name_to_atom(ShortId, Label),
 1389    send_super(TF, initialise, Label),
 1390    send(TF, name, Path),
 1391    send(TF, slot, path, Path).
 1392
 1393:- pce_global(@xref_file_text_recogniser,
 1394              make_xref_file_text_recogniser). 1395
 1396make_xref_file_text_recogniser(G) :-
 1397    new(C, click_gesture(left, '', single,
 1398                         message(@receiver, run_default_action))),
 1399    new(P, popup_gesture(@arg1?popup)),
 1400    new(D, drag_and_drop_gesture(left)),
 1401    send(D, cursor, @default),
 1402    new(G, handler_group(C, D, P, @arm_recogniser)).
 1403
 1404popup(_, Popup:popup) :<-
 1405    new(Popup, popup),
 1406    send_list(Popup, append,
 1407              [ menu_item(edit, message(@arg1, edit)),
 1408                menu_item(info, message(@arg1, info)),
 1409                menu_item(header, message(@arg1, header))
 1410              ]).
 1411
 1412event(T, Ev:event) :->
 1413    (   send_super(T, event, Ev)
 1414    ->  true
 1415    ;   send(@xref_file_text_recogniser, event, Ev)
 1416    ).
 1417
 1418arm(TF, Val:bool) :->
 1419    "Preview activity"::
 1420    (   Val == @on
 1421    ->  send(TF, underline, @on),
 1422        send(TF, report, status, 'File %s', TF?path)
 1423    ;   send(TF, underline, @off),
 1424        send(TF, report, status, '')
 1425    ).
 1426
 1427run_default_action(T) :->
 1428    get(T, default_action, Def),
 1429    send(T, Def).
 1430
 1431edit(T) :->
 1432    get(T, path, Path),
 1433    edit(file(Path)).
 1434
 1435info(T) :->
 1436    get(T, path, Path),
 1437    send(T?frame, file_info, Path).
 1438
 1439header(T) :->
 1440    get(T, path, Path),
 1441    send(T?frame, file_header, Path).
 1442
 1443prolog_source(T, Src:string) :<-
 1444    "Import declarations"::
 1445    get(T, path, File),
 1446    new(V, xref_view),
 1447    send(V, file_header, File),
 1448    get(V?text_buffer, contents, Src),
 1449    send(V, destroy).
 1450
 1451:- pce_end_class(xref_file_text).
 1452
 1453
 1454:- pce_begin_class(xref_directory_text, text,
 1455                   "Represent a directory-name").
 1456
 1457variable(path,           name,         get, "Filename represented").
 1458
 1459initialise(TF, Dir:name, Label:[name]) :->
 1460    absolute_file_name(Dir, Path),
 1461    (   Label == @default
 1462    ->  file_base_name(Path, TheLabel)
 1463    ;   TheLabel = Label
 1464    ),
 1465    send_super(TF, initialise, TheLabel),
 1466    send(TF, slot, path, Path).
 1467
 1468files(DT, Files:chain) :<-
 1469    "List of files that belong to this directory"::
 1470    new(Files, chain),
 1471    get(DT, path, Path),
 1472    (   source_file(File),
 1473        sub_atom(File, 0, _, _, Path),
 1474        send(Files, append, File),
 1475        fail ; true
 1476    ).
 1477
 1478:- pce_global(@xref_directory_text_recogniser,
 1479              make_xref_directory_text_recogniser). 1480
 1481make_xref_directory_text_recogniser(G) :-
 1482    new(D, drag_and_drop_gesture(left)),
 1483    send(D, cursor, @default),
 1484    new(G, handler_group(D, @arm_recogniser)).
 1485
 1486event(T, Ev:event) :->
 1487    (   send_super(T, event, Ev)
 1488    ->  true
 1489    ;   send(@xref_directory_text_recogniser, event, Ev)
 1490    ).
 1491
 1492arm(TF, Val:bool) :->
 1493    "Preview activiity"::
 1494    (   Val == @on
 1495    ->  send(TF, underline, @on),
 1496        send(TF, report, status, 'Directory %s', TF?path)
 1497    ;   send(TF, underline, @off),
 1498        send(TF, report, status, '')
 1499    ).
 1500
 1501:- pce_end_class(xref_directory_text).
 1502
 1503
 1504:- pce_begin_class(xref_imported_by, figure,
 1505                   "Indicate import of callable into file").
 1506
 1507variable(callable, prolog, get, "Callable term of imported predicate").
 1508
 1509:- pce_global(@xref_horizontal_format,
 1510              make_xref_horizontal_format). 1511
 1512make_xref_horizontal_format(F) :-
 1513    new(F, format(vertical, 1, @on)),
 1514    send(F, row_sep, 3),
 1515    send(F, column_sep, 0).
 1516
 1517initialise(IT, File:name, Imported:prolog) :->
 1518    send_super(IT, initialise),
 1519    send(IT, format, @xref_horizontal_format),
 1520    send(IT, display, new(F, xref_file_text(File))),
 1521    send(F, name, file_text),
 1522    send(IT, slot, callable, Imported),
 1523    send(IT, show_called_by).
 1524
 1525path(IT, Path:name) :<-
 1526    "Represented file"::
 1527    get(IT, member, file_text, Text),
 1528    get(Text, path, Path).
 1529
 1530show_called_by(IT) :->
 1531    "Add number indicating calls"::
 1532    get(IT, called_by, List),
 1533    length(List, N),
 1534    send(IT, display, new(T, text(string('(%d)', N)))),
 1535    send(T, name, called_count),
 1536    (   N > 0
 1537    ->  send(T, underline, @on),
 1538        send(T, colour, blue),
 1539        send(T, recogniser, @xref_called_by_recogniser)
 1540    ;   send(T, colour, grey60)
 1541    ).
 1542
 1543called_by(IT, ByList:prolog) :<-
 1544    "Return list of callables satisfied by the import"::
 1545    get(IT, path, Source),
 1546    get(IT, callable, Callable),
 1547    findall(By, used_in(Source, Callable, By), ByList).
 1548
 1549%!  used_in(+Source, +QCallable, -CalledBy)
 1550%
 1551%   Determine which the callers for   QCallable in Source. QCallable
 1552%   is qualified with the module of the exporting file (if any).
 1553
 1554used_in(Source, M:Callable, By) :-              % we are the same module
 1555    xref_module(Source, M),
 1556    !,
 1557    xref_called(Source, Callable, By).
 1558used_in(Source, _:Callable, By) :-              % we imported
 1559    xref_defined(Source, Callable, imported(_)),
 1560    !,
 1561    xref_called(Source, Callable, By).
 1562used_in(Source, Callable, By) :-
 1563    xref_called(Source, Callable, By).
 1564used_in(Source, Callable, '<export>') :-
 1565    xref_exported(Source, Callable).
 1566
 1567:- pce_group(event).
 1568
 1569:- pce_global(@xref_called_by_recogniser,
 1570              new(popup_gesture(@receiver?device?called_by_popup, left))).
 1571
 1572called_by_popup(IT, P:popup) :<-
 1573    "Show called where import is called"::
 1574    new(P, popup(called_by, message(IT, edit_called_by, @arg1))),
 1575    get(IT, called_by, ByList),
 1576    sort_callables(ByList, Sorted),
 1577    forall(member(C, Sorted),
 1578           ( callable_to_label(C, Label),
 1579             send(P, append, menu_item(prolog(C), @default, Label)))).
 1580
 1581edit_called_by(IT, Called:prolog) :->
 1582    "Edit file on the predicate Called"::
 1583    get(IT, path, Source),
 1584    edit_callable(Called, Source).
 1585
 1586:- pce_end_class(xref_imported_by).
 1587
 1588
 1589:- pce_begin_class(xref_graphical_list, figure,
 1590                   "Show list of exports to files").
 1591
 1592variable(wrap, {extend,wrap,wrap_fixed_width,clip} := extend, get,
 1593         "Wrapping mode").
 1594
 1595initialise(XL) :->
 1596    send_super(XL, initialise),
 1597    send(XL, margin, 500, wrap).
 1598
 1599append(XL, I:graphical) :->
 1600    (   send(XL?graphicals, empty)
 1601    ->  true
 1602    ;   send(XL, display, text(', '))
 1603    ),
 1604    send(XL, display, I).
 1605
 1606:- pce_group(layout).
 1607
 1608:- pce_global(@xref_graphical_list_format,
 1609              make_xref_graphical_list_format). 1610
 1611make_xref_graphical_list_format(F) :-
 1612    new(F, format(horizontal, 500, @off)),
 1613    send(F, column_sep, 0),
 1614    send(F, row_sep, 0).
 1615
 1616margin(T, Width:int*, How:[{wrap,wrap_fixed_width,clip}]) :->
 1617    "Wrap items to indicated width"::
 1618    (   Width == @nil
 1619    ->  send(T, slot, wrap, extend),
 1620        send(T, format, @rdf_composite_format)
 1621    ;   send(T, slot, wrap, How),
 1622        How == wrap
 1623    ->  FmtWidth is max(10, Width),
 1624        new(F, format(horizontal, FmtWidth, @off)),
 1625        send(F, column_sep, 0),
 1626        send(F, row_sep, 0),
 1627        send(T, format, F)
 1628    ;   throw(tbd)
 1629    ).
 1630
 1631:- pce_end_class(xref_graphical_list).
 1632
 1633
 1634
 1635                 /*******************************
 1636                 *          PREDICATES          *
 1637                 *******************************/
 1638
 1639:- pce_begin_class(xref_predicate_browser, browser,
 1640                 "Show loaded files").
 1641
 1642initialise(PL) :->
 1643    send_super(PL, initialise),
 1644    send(PL, popup, new(P, popup)),
 1645    send_list(P, append,
 1646              [ menu_item(edit, message(@arg1, edit))
 1647              ]).
 1648
 1649update(PL) :->
 1650    send(PL, clear),
 1651    forall((defined(File, Callable), atom(File), \+ library_file(File)),
 1652           send(PL, append, Callable, @default, File)),
 1653    forall((xref_current_source(File), atom(File), \+library_file(File)),
 1654           forall(undefined(File, Callable),
 1655                  send(PL, append, Callable, undefined, File))),
 1656    send(PL, sort).
 1657
 1658append(PL, Callable:prolog, Class:[name], File:[name]) :->
 1659    send_super(PL, append, xref_predicate_dict_item(Callable, Class, File)).
 1660
 1661:- pce_end_class(xref_predicate_browser).
 1662
 1663
 1664:- pce_begin_class(xref_predicate_dict_item, dict_item,
 1665                   "Represent a Prolog predicate").
 1666
 1667variable(callable, prolog, get, "Callable term").
 1668variable(file,     name*,  get, "Origin file").
 1669
 1670initialise(PI, Callable0:prolog, _Class:[name], File:[name]) :->
 1671    "Create from callable, class and file"::
 1672    single_qualify(Callable0, Callable),
 1673    send(PI, slot, callable, Callable),
 1674    callable_to_label(Callable, Label),
 1675    send_super(PI, initialise, Label),
 1676    (   File \== @default
 1677    ->  send(PI, slot, file, File)
 1678    ;   true
 1679    ).
 1680
 1681edit(PI) :->
 1682    "Edit Associated prediate"::
 1683    get(PI, file, File),
 1684    get(PI, callable, Callable),
 1685    edit_callable(Callable, File).
 1686
 1687:- pce_end_class(xref_predicate_dict_item).
 1688
 1689
 1690                 /*******************************
 1691                 *         UTIL CLASSES         *
 1692                 *******************************/
 1693
 1694:- pce_begin_class(xref_view, view,
 1695                   "View with additional facilities for formatting").
 1696
 1697initialise(V) :->
 1698    send_super(V, initialise),
 1699    send(V, font, fixed).
 1700
 1701update(_) :->
 1702    true.                           % or ->clear?  ->destroy?
 1703
 1704file_header(View, File:name) :->
 1705    "Create import/export fileheader for File"::
 1706    (   xref_module(File, _)
 1707    ->  Decls = Imports
 1708    ;   xref_file_exports(File, Export),
 1709        Decls = [Export|Imports]
 1710    ),
 1711    xref_file_imports(File, Imports),
 1712    send(View, clear),
 1713    send(View, declarations, Decls),
 1714    (   (   nonvar(Export)
 1715        ->  send(View, report, status,
 1716                 'Created module header for non-module file %s', File)
 1717        ;   send(View, report, status,
 1718                 'Created import header for module file %s', File)
 1719        )
 1720    ->  true
 1721    ;   true
 1722    ).
 1723
 1724declarations(V, Decls:prolog) :->
 1725    pce_open(V, append, Out),
 1726    call_cleanup(print_decls(Decls, Out), close(Out)).
 1727
 1728print_decls([], _) :- !.
 1729print_decls([H|T], Out) :-
 1730    !,
 1731    print_decls(H, Out),
 1732    print_decls(T, Out).
 1733print_decls(Term, Out) :-
 1734    portray_clause(Out, Term).
 1735
 1736:- pce_end_class(xref_view).
 1737
 1738
 1739                 /*******************************
 1740                 *        FILE-NAME LOGIC       *
 1741                 *******************************/
 1742
 1743%!  short_file_name_to_atom(+ShortId, -Atom)
 1744%
 1745%   Convert a short filename into an atom
 1746
 1747short_file_name_to_atom(Atom, Atom) :-
 1748    atomic(Atom),
 1749    !.
 1750short_file_name_to_atom(Term, Atom) :-
 1751    term_to_atom(Term, Atom).
 1752
 1753
 1754%!  library_file(+Path)
 1755%
 1756%   True if Path comes from the Prolog tree and must be considered a
 1757%   library.
 1758
 1759library_file(Path) :-
 1760    current_prolog_flag(home, Home),
 1761    sub_atom(Path, 0, _, _, Home).
 1762
 1763%!  profile_file(+Path)
 1764%
 1765%   True if path is a personalisation file.  This is a bit hairy.
 1766
 1767profile_file(Path) :-
 1768    file_name_on_path(Path, user_profile(File)),
 1769    known_profile_file(File).
 1770
 1771known_profile_file('.swiplrc').
 1772known_profile_file('swipl.ini').
 1773known_profile_file('.pceemacsrc').
 1774known_profile_file(File) :-
 1775    sub_atom(File, 0, _, _, 'lib/xpce/emacs').
 1776
 1777%!  sort_files(+Files, -Sorted)
 1778%
 1779%   Sort files, keeping groups comming from the same alias together.
 1780
 1781sort_files(Files0, Sorted) :-
 1782    sort(Files0, Files),            % remove duplicates
 1783    maplist(key_file, Files, Keyed),
 1784    keysort(Keyed, KSorted),
 1785    unkey(KSorted, Sorted).
 1786
 1787key_file(File, Key-File) :-
 1788    file_name_on_path(File, Key).
 1789
 1790
 1791                 /*******************************
 1792                 *           PREDICATES         *
 1793                 *******************************/
 1794
 1795%!  available(+File, +Callable, -HowDefined)
 1796%
 1797%   True if Callable is available in File.
 1798
 1799available(File, Called, How) :-
 1800    xref_defined(File, Called, How0),
 1801    !,
 1802    How = How0.
 1803available(_, Called, How) :-
 1804    built_in_predicate(Called),
 1805    !,
 1806    How = builtin.
 1807available(_, Called, How) :-
 1808    setting(warn_autoload, false),
 1809    autoload_predicate(Called),
 1810    !,
 1811    How = autoload.
 1812available(_, Called, How) :-
 1813    setting(warn_autoload, false),
 1814    global_predicate(Called),
 1815    !,
 1816    How = global.
 1817available(_, Called, How) :-
 1818    Called = _:_,
 1819    defined(_, Called),
 1820    !,
 1821    How = module_qualified.
 1822available(_, M:G, How) :-
 1823    defined(ExportFile, G),
 1824    xref_module(ExportFile, M),
 1825    !,
 1826    How = module_overruled.
 1827available(_, Called, How) :-
 1828    defined(ExportFile, Called),
 1829    \+ xref_module(ExportFile, _),
 1830    !,
 1831    How == plain_file.
 1832
 1833
 1834%!  built_in_predicate(+Callable)
 1835%
 1836%   True if Callable is a built-in
 1837
 1838built_in_predicate(Goal) :-
 1839    strip_module(Goal, _, Plain),
 1840    xref_built_in(Plain).
 1841
 1842%!  autoload_predicate(+Callable) is semidet.
 1843%!  autoload_predicate(+Callable, -File) is semidet.
 1844%
 1845%   True if Callable can be autoloaded.  TBD: make sure the autoload
 1846%   index is up-to-date.
 1847
 1848autoload_predicate(Goal) :-
 1849    '$autoload':library_index(Goal, _, _).
 1850
 1851
 1852autoload_predicate(Goal, File) :-
 1853    '$autoload':library_index(Goal, _, FileNoExt),
 1854    file_name_extension(FileNoExt, pl, File).
 1855
 1856
 1857%!  global_predicate(+Callable)
 1858%
 1859%   True if Callable can  be  auto-imported   from  the  global user
 1860%   module.
 1861
 1862global_predicate(Goal) :-
 1863    predicate_property(user:Goal, _),
 1864    !.
 1865
 1866%!  to_predicate_indicator(+Term, -PI)
 1867%
 1868%   Convert to a predicate indicator.
 1869
 1870to_predicate_indicator(PI, PI) :-
 1871    is_predicate_indicator(PI),
 1872    !.
 1873to_predicate_indicator(Callable, PI) :-
 1874    callable(Callable),
 1875    predicate_indicator(Callable, PI).
 1876
 1877%!  is_predicate_indicator(+PI) is semidet.
 1878%
 1879%   True if PI is a predicate indicator.
 1880
 1881is_predicate_indicator(Name/Arity) :-
 1882    atom(Name),
 1883    integer(Arity).
 1884is_predicate_indicator(Module:Name/Arity) :-
 1885    atom(Module),
 1886    atom(Name),
 1887    integer(Arity).
 1888
 1889%!  predicate_indicator(+Callable, -Name)
 1890%
 1891%   Generate a human-readable predicate indicator
 1892
 1893predicate_indicator(Module:Goal, PI) :-
 1894    atom(Module),
 1895    !,
 1896    predicate_indicator(Goal, PI0),
 1897    (   hidden_module(Module)
 1898    ->  PI = PI0
 1899    ;   PI = Module:PI0
 1900    ).
 1901predicate_indicator(Goal, Name/Arity) :-
 1902    callable(Goal),
 1903    !,
 1904    functor(Goal, Name, Arity).
 1905predicate_indicator(Goal, Goal).
 1906
 1907hidden_module(user) :- !.
 1908hidden_module(system) :- !.
 1909hidden_module(M) :-
 1910    sub_atom(M, 0, _, _, $).
 1911
 1912%!  sort_callables(+List, -Sorted)
 1913%
 1914%   Sort list of callable terms.
 1915
 1916sort_callables(Callables, Sorted) :-
 1917    key_callables(Callables, Tagged),
 1918    keysort(Tagged, KeySorted),
 1919    unkey(KeySorted, SortedList),
 1920    ord_list_to_set(SortedList, Sorted).
 1921
 1922key_callables([], []).
 1923key_callables([H0|T0], [Key-H0|T]) :-
 1924    key_callable(H0, Key),
 1925    key_callables(T0, T).
 1926
 1927key_callable(Callable, k(Name, Arity, Module)) :-
 1928    predicate_indicator(Callable, PI),
 1929    (   PI = Name/Arity
 1930    ->  Module = user
 1931    ;   PI = Module:Name/Arity
 1932    ).
 1933
 1934unkey([], []).
 1935unkey([_-H|T0], [H|T]) :-
 1936    unkey(T0, T).
 1937
 1938%!  ord_list_to_set(+OrdList, -OrdSet)
 1939%
 1940%   Removed duplicates (after unification) from an ordered list,
 1941%   creating a set.
 1942
 1943ord_list_to_set([], []).
 1944ord_list_to_set([H|T0], [H|T]) :-
 1945    ord_remove_same(H, T0, T1),
 1946    ord_list_to_set(T1, T).
 1947
 1948ord_remove_same(H, [H|T0], T) :-
 1949    !,
 1950    ord_remove_same(H, T0, T).
 1951ord_remove_same(_, L, L).
 1952
 1953
 1954%!  callable_to_label(+Callable, +File, -Label:atom) is det.
 1955%!  callable_to_label(+Callable, -Label:atom) is det.
 1956%
 1957%   Label is a textual label representing Callable in File.
 1958
 1959callable_to_label(Callable, Label) :-
 1960    callable_to_label(Callable, @nil, Label).
 1961
 1962callable_to_label(pce_principal:send_implementation(Id,_,_), _, Id) :-
 1963    atom(Id),
 1964    !.
 1965callable_to_label(pce_principal:get_implementation(Id,_,_,_), _, Id) :-
 1966    atom(Id),
 1967    !.
 1968callable_to_label('<export>', _, '<export>') :- !.
 1969callable_to_label('<directive>'(Line), _, Label) :-
 1970    !,
 1971    atom_concat('<directive>@', Line, Label).
 1972callable_to_label(_:'<directive>'(Line), _, Label) :-
 1973    !,
 1974    atom_concat('<directive>@', Line, Label).
 1975callable_to_label(Callable, File, Label) :-
 1976    to_predicate_indicator(Callable, PI0),
 1977    (   PI0 = M:PI1
 1978    ->  (   atom(File),
 1979            xref_module(File, M)
 1980        ->  PI = PI1
 1981        ;   PI = PI0
 1982        )
 1983    ;   PI = PI0
 1984    ),
 1985    term_to_atom(PI, Label).
 1986
 1987%!  edit_callable(+Callable, +File)
 1988
 1989edit_callable('<export>', File) :-
 1990    !,
 1991    edit(file(File)).
 1992edit_callable(Callable, File) :-
 1993    local_callable(Callable, File, Local),
 1994    (   xref_defined(File, Local, How),
 1995        xref_definition_line(How, Line)
 1996    ->  edit(file(File, line(Line)))
 1997    ;   autoload_predicate(Local)
 1998    ->  functor(Local, Name, Arity),
 1999        edit(Name/Arity)
 2000    ).
 2001edit_callable(pce_principal:send_implementation(Id,_,_), _) :-
 2002    atom(Id),
 2003    atomic_list_concat([Class,Method], ->, Id),
 2004    !,
 2005    edit(send(Class, Method)).
 2006edit_callable(pce_principal:get_implementation(Id,_,_,_), _) :-
 2007    atom(Id),
 2008    atomic_list_concat([Class,Method], <-, Id),
 2009    !,
 2010    edit(get(Class, Method)).
 2011edit_callable('<directive>'(Line), File) :-
 2012    File \== @nil,
 2013    !,
 2014    edit(file(File, line(Line))).
 2015edit_callable(_:'<directive>'(Line), File) :-
 2016    File \== @nil,
 2017    !,
 2018    edit(file(File, line(Line))).
 2019edit_callable(Callable, _) :-
 2020    to_predicate_indicator(Callable, PI),
 2021    edit(PI).
 2022
 2023local_callable(M:Callable, File, Callable) :-
 2024    xref_module(File, M),
 2025    !.
 2026local_callable(Callable, _, Callable).
 2027
 2028
 2029                 /*******************************
 2030                 *            WARNINGS          *
 2031                 *******************************/
 2032
 2033%!  file_warnings(+File:atom, -Warnings:list(atom))
 2034%
 2035%   Unify Warnings with a list  of   dubious  things  found in File.
 2036%   Intended to create icons.  Fails if the file is totally ok.
 2037
 2038file_warnings(File, Warnings) :-
 2039    setof(W, file_warning(File, W), Warnings).
 2040
 2041file_warning(File, undefined) :-
 2042    undefined(File, _) -> true.
 2043file_warning(File, not_called) :-
 2044    setting(warn_not_called, true),
 2045    not_called(File, _) -> true.
 2046
 2047
 2048%!  not_called(+File, -Callable)
 2049%
 2050%   Callable is a term defined in File, and for which no callers can
 2051%   be found.
 2052
 2053not_called(File, NotCalled) :-          % module version
 2054    xref_module(File, Module),
 2055    !,
 2056    defined(File, NotCalled),
 2057    \+ (   xref_called(File, NotCalled)
 2058       ;   xref_exported(File, NotCalled)
 2059       ;   xref_hook(NotCalled)
 2060       ;   xref_hook(Module:NotCalled)
 2061       ;   NotCalled = _:Goal,
 2062           xref_hook(Goal)
 2063       ;   xref_called(_, Module:NotCalled)
 2064       ;   NotCalled = _:_,
 2065           xref_called(_, NotCalled)
 2066       ;   NotCalled = M:G,
 2067           xref_called(ModFile, G),
 2068           xref_module(ModFile, M)
 2069       ;   generated_callable(Module:NotCalled)
 2070       ).
 2071not_called(File, NotCalled) :-          % non-module version
 2072    defined(File, NotCalled),
 2073    \+ (   xref_called(ImportFile, NotCalled),
 2074           \+ xref_module(ImportFile, _)
 2075       ;   NotCalled = _:_,
 2076           xref_called(_, NotCalled)
 2077       ;   NotCalled = M:G,
 2078           xref_called(ModFile, G),
 2079           xref_module(ModFile, M)
 2080       ;   xref_called(AutoImportFile, NotCalled),
 2081           \+ defined(AutoImportFile, NotCalled),
 2082           global_predicate(NotCalled)
 2083       ;   xref_hook(NotCalled)
 2084       ;   xref_hook(user:NotCalled)
 2085       ;   generated_callable(user:NotCalled)
 2086       ).
 2087
 2088generated_callable(M:Term) :-
 2089    functor(Term, Name, Arity),
 2090    prolog:generated_predicate(M:Name/Arity).
 2091
 2092%!  xref_called(?Source, ?Callable) is nondet.
 2093%
 2094%   True if Callable is called in   Source, after removing recursive
 2095%   calls and calls made to predicates where the condition says that
 2096%   the predicate should not exist.
 2097
 2098xref_called(Source, Callable) :-
 2099    xref_called_cond(Source, Callable, _).
 2100
 2101xref_called_cond(Source, Callable, Cond) :-
 2102    xref_called(Source, Callable, By, Cond),
 2103    By \= Callable.                 % recursive calls
 2104
 2105%!  defined(?File, ?Callable)
 2106%
 2107%   True if Callable is defined in File and not imported.
 2108
 2109defined(File, Callable) :-
 2110    xref_defined(File, Callable, How),
 2111    atom(File),
 2112    How \= imported(_),
 2113    How \= (multifile).
 2114
 2115%!  undefined(+File, -Callable)
 2116%
 2117%   Callable is called in File, but no   definition can be found. If
 2118%   File is not a module file we   consider other files that are not
 2119%   module files.
 2120
 2121undefined(File, Undef) :-
 2122    xref_module(File, _),
 2123    !,
 2124    xref_called_cond(File, Undef, Cond),
 2125    \+ (   available(File, Undef, How),
 2126           How \== plain_file
 2127       ),
 2128    included_if_defined(Cond, Undef).
 2129undefined(File, Undef) :-
 2130    xref_called_cond(File, Undef, Cond),
 2131    \+ available(File, Undef, _),
 2132    included_if_defined(Cond, Undef).
 2133
 2134%!  included_if_defined(+Condition, +Callable) is semidet.
 2135
 2136included_if_defined(true, _)  :- !.
 2137included_if_defined(false, _) :- !, fail.
 2138included_if_defined(fail, _)  :- !, fail.
 2139included_if_defined(current_predicate(Name/Arity), Callable) :-
 2140    \+ functor(Callable, Name, Arity),
 2141    !.
 2142included_if_defined(\+ Cond, Callable) :-
 2143    !,
 2144    \+ included_if_defined(Cond, Callable).
 2145included_if_defined((A,B), Callable) :-
 2146    !,
 2147    included_if_defined(A, Callable),
 2148    included_if_defined(B, Callable).
 2149included_if_defined((A;B), Callable) :-
 2150    !,
 2151    (   included_if_defined(A, Callable)
 2152    ;   included_if_defined(B, Callable)
 2153    ).
 2154
 2155
 2156                 /*******************************
 2157                 *    IMPORT/EXPORT HEADERS     *
 2158                 *******************************/
 2159
 2160%!  file_imports(+File, -Imports)
 2161%
 2162%   Determine which modules must  be  imported   into  this  one. It
 2163%   considers all called predicates that are   not covered by system
 2164%   predicates. Next, we have three sources to resolve the remaining
 2165%   predicates, which are tried in the   order below. The latter two
 2166%   is dubious.
 2167%
 2168%           * Already existing imports
 2169%           * Imports from other files in the project
 2170%           * Imports from the (autoload) library
 2171%
 2172%   We first resolve all imports to   absolute  files. Localizing is
 2173%   done afterwards.  Imports is a list of
 2174%
 2175%!          use_module(FileSpec, Callables)
 2176
 2177xref_file_imports(FileSpec, Imports) :-
 2178    canonical_filename(FileSpec, File),
 2179    findall(Called, called_no_builtin(File, Called), Resolve0),
 2180    resolve_old_imports(Resolve0, File, Resolve1, Imports0),
 2181    find_new_imports(Resolve1, File, Imports1),
 2182    disambiguate_imports(Imports1, File, Imports2),
 2183    flatten([Imports0, Imports2], ImportList),
 2184    keysort(ImportList, SortedByFile),
 2185    merge_by_key(SortedByFile, ImportsByFile),
 2186    maplist(make_import(File), ImportsByFile, Imports).
 2187
 2188canonical_filename(FileSpec, File) :-
 2189    absolute_file_name(FileSpec,
 2190                       [ file_type(prolog),
 2191                         access(read),
 2192                         file_errors(fail)
 2193                       ],
 2194                       File).
 2195
 2196called_no_builtin(File, Callable) :-
 2197    xref_called(File, Callable),
 2198    \+ defined(File, Callable),
 2199    \+ built_in_predicate(Callable).
 2200
 2201resolve_old_imports([], _, [], []).
 2202resolve_old_imports([H|T0], File, UnRes, [From-H|T]) :-
 2203    xref_defined(File, H, imported(From)),
 2204    !,
 2205    resolve_old_imports(T0, File, UnRes, T).
 2206resolve_old_imports([H|T0], File, [H|UnRes], Imports) :-
 2207    resolve_old_imports(T0, File, UnRes, Imports).
 2208
 2209find_new_imports([], _, []).
 2210find_new_imports([H|T0], File, [FL-H|T]) :-
 2211    findall(F, resolve(H, F), FL0),
 2212    sort(FL0, FL),
 2213    find_new_imports(T0, File, T).
 2214
 2215disambiguate_imports(Imports0, File, Imports) :-
 2216    ambiguous_imports(Imports0, Ambig, UnAmbig, _Undef),
 2217    (   Ambig == []
 2218    ->  Imports = UnAmbig
 2219    ;   new(D, xref_disambiguate_import_dialog(File, Ambig)),
 2220        get(D, confirm_centered, Result),
 2221        (   Result == ok
 2222        ->  get(D, result, List),
 2223            send(D, destroy),
 2224            append(UnAmbig, List, Imports)
 2225        )
 2226    ).
 2227
 2228ambiguous_imports([], [], [], []).
 2229ambiguous_imports([[]-C|T0], Ambig, UnAmbig, [C|T]) :-
 2230    !,
 2231    ambiguous_imports(T0, Ambig, UnAmbig, T).
 2232ambiguous_imports([[F]-C|T0], Ambig, [F-C|T], Undef) :-
 2233    !,
 2234    ambiguous_imports(T0, Ambig, T, Undef).
 2235ambiguous_imports([A-C|T0], [A-C|T], UnAmbig, Undef) :-
 2236    is_list(A),
 2237    !,
 2238    ambiguous_imports(T0, T, UnAmbig, Undef).
 2239
 2240
 2241%!  resolve(+Callable, -File)
 2242%
 2243%   Try to find files from which to resolve Callable.
 2244
 2245resolve(Callable, File) :-              % Export from module files
 2246    xref_exported(File, Callable),
 2247    atom(File).
 2248resolve(Callable, File) :-              % Non-module files
 2249    defined(File, Callable),
 2250    atom(File),
 2251    \+ xref_module(File, _).
 2252resolve(Callable, File) :-              % The Prolog autoload library
 2253    autoload_predicate(Callable, File).
 2254
 2255
 2256%!  merge_by_key(+KeyedList, -ListOfKeyValues) is det.
 2257%
 2258%   Example: [a-x, a-y, b-z] --> [a-[x,y], b-[z]]
 2259
 2260merge_by_key([], []).
 2261merge_by_key([K-V|T0], [K-[V|Vs]|T]) :-
 2262    same_key(K, T0, Vs, T1),
 2263    merge_by_key(T1, T).
 2264
 2265same_key(K, [K-V|T0], [V|VT], T) :-
 2266    !,
 2267    same_key(K, T0, VT, T).
 2268same_key(_, L, [], L).
 2269
 2270
 2271%!  make_import(+RefFile, +ImportList, -UseModules)
 2272%
 2273%   Glues it all together to make a list of directives.
 2274
 2275make_import(RefFile, File-Imports, (:-use_module(ShortPath, PIs))) :-
 2276    local_filename(File, RefFile, ShortPath),
 2277    sort_callables(Imports, SortedImports),
 2278    maplist(predicate_indicator, SortedImports, PIs).
 2279
 2280local_filename(File, RefFile, ShortPath) :-
 2281    atom(RefFile),
 2282    file_directory_name(File, Dir),
 2283    file_directory_name(RefFile, Dir),     % i.e. same dir
 2284    !,
 2285    file_base_name(File, Base),
 2286    remove_extension(Base, ShortPath).
 2287local_filename(File, _RefFile, ShortPath) :-
 2288    file_name_on_path(File, ShortPath0),
 2289    remove_extension(ShortPath0, ShortPath).
 2290
 2291
 2292remove_extension(Term0, Term) :-
 2293    Term0 =.. [Alias,ShortPath0],
 2294    file_name_extension(ShortPath, pl, ShortPath0),
 2295    !,
 2296    Term  =.. [Alias,ShortPath].
 2297remove_extension(ShortPath0, ShortPath) :-
 2298    atom(ShortPath0),
 2299    file_name_extension(ShortPath, pl, ShortPath0),
 2300    !.
 2301remove_extension(Path, Path).
 2302
 2303:- pce_begin_class(xref_disambiguate_import_dialog, auto_sized_dialog,
 2304                   "Prompt for alternative sources").
 2305
 2306initialise(D, File:name, Ambig:prolog) :->
 2307    send_super(D, initialise, string('Disambiguate calls for %s', File)),
 2308    forall(member(Files-Callable, Ambig),
 2309           send(D, append_row, File, Callable, Files)),
 2310    send(D, append, button(ok)),
 2311    send(D, append, button(cancel)).
 2312
 2313append_row(D, File:name, Callable:prolog, Files:prolog) :->
 2314    send(D, append, xref_predicate_text(Callable, @default, File)),
 2315    send(D, append, new(FM, menu(file, cycle)), right),
 2316    send(FM, append, menu_item(@nil, @default, '-- Select --')),
 2317    forall(member(Path, Files),
 2318           (   file_name_on_path(Path, ShortId),
 2319               short_file_name_to_atom(ShortId, Label),
 2320               send(FM, append, menu_item(Path, @default, Label))
 2321           )).
 2322
 2323result(D, Disam:prolog) :<-
 2324    "Get disambiguated files"::
 2325    get_chain(D, graphicals, Grs),
 2326    selected_files(Grs, Disam).
 2327
 2328selected_files([], []).
 2329selected_files([PreText,Menu|T0], [File-Callable|T]) :-
 2330    send(PreText, instance_of, xref_predicate_text),
 2331    send(Menu, instance_of, menu),
 2332    get(Menu, selection, File),
 2333    atom(File),
 2334    !,
 2335    get(PreText, callable, Callable),
 2336    selected_files(T0, T).
 2337selected_files([_|T0], T) :-
 2338    selected_files(T0, T).
 2339
 2340
 2341ok(D) :->
 2342    send(D, return, ok).
 2343
 2344cancel(D) :->
 2345    send(D, destroy).
 2346
 2347:- pce_end_class(xref_disambiguate_import_dialog).
 2348
 2349%!  xref_file_exports(+File, -Exports)
 2350%
 2351%   Produce the export-header for non-module files.  Fails if the
 2352%   file is already a module file.
 2353
 2354xref_file_exports(FileSpec, (:- module(Module, Exports))) :-
 2355    canonical_filename(FileSpec, File),
 2356    \+ xref_module(File, _),
 2357    findall(C, export_link_1(File, _, C), Cs),
 2358    sort_callables(Cs, Sorted),
 2359    file_base_name(File, Base),
 2360    file_name_extension(Module, _, Base),
 2361    maplist(predicate_indicator, Sorted, Exports)