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:           https://www.swi-prolog.org
    6    Copyright (c)  1998-2026, University of Amsterdam
    7                              VU University Amsterdam
    8                              SWI-Prolog Solutions b.v.
    9    All rights reserved.
   10
   11    Redistribution and use in source and binary forms, with or without
   12    modification, are permitted provided that the following conditions
   13    are met:
   14
   15    1. Redistributions of source code must retain the above copyright
   16       notice, this list of conditions and the following disclaimer.
   17
   18    2. Redistributions in binary form must reproduce the above copyright
   19       notice, this list of conditions and the following disclaimer in
   20       the documentation and/or other materials provided with the
   21       distribution.
   22
   23    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   24    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   25    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   26    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   27    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   28    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   29    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   30    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   31    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   32    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   33    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   34    POSSIBILITY OF SUCH DAMAGE.
   35*/
   36
   37:- module(prolog_edit,
   38          [ edit/1,                     % +Spec
   39            edit/0
   40          ]).   41:- autoload(library(lists), [member/2, append/3, select/3, append/2]).   42:- autoload(library(make), [make/0]).   43:- autoload(library(prolog_breakpoints), [breakpoint_property/2]).   44:- autoload(library(apply), [foldl/5, maplist/3, maplist/2]).   45:- use_module(library(dcg/high_order), [sequence/5]).   46:- autoload(library(readutil), [read_line_to_string/2]).   47:- autoload(library(solution_sequences), [distinct/2]).   48
   49
   50% :- set_prolog_flag(generate_debug_info, false).

Editor interface

This module implements the generic editor interface. It consists of two extensible parts with little in between. The first part deals with translating the input into source-location, and the second with starting an editor.

   60:- multifile
   61    locate/3,                       % +Partial, -FullSpec, -Location
   62    locate/2,                       % +FullSpec, -Location
   63    select_location/3,              % +Pairs, +Spec, -Location
   64    exists_location/1,              % +Location
   65    user_select/2,                  % +Max, -I
   66    edit_source/1,                  % +Location
   67    edit_command/2,                 % +Editor, -Command
   68    load/0.                         % provides load-hooks
   69
   70:- public
   71    locations/2,                    % +Spec, -Locations
   72    predicate_location/2,           % :Pred, -Location
   73    addr2location/3.                % +Address, -File, -Line
 edit(+Spec)
Edit indicated object. Spec is a predicate indicator, a file specification or a source location File:Line or File:Line:Col. Both Line and Col count from 1, as in the messages we print, the hyperlinks we emit (see ansi_hyperlink/2) and the messages of e.g., the C compiler. Note that the line_position of a stream (see stream_position_data/3) counts from 0.
   84edit(Spec) :-
   85    notrace(edit_no_trace(Spec)).
   86
   87edit_no_trace(Spec) :-
   88    var(Spec),
   89    !,
   90    throw(error(instantiation_error, _)).
   91edit_no_trace(Spec) :-
   92    locations(Spec, Pairs),
   93    do_select_location(Pairs, Spec, Location),
   94    do_edit_source(Location).
 edit
Edit associated or script file. This is the Prolog file opened by double-clicking or the file loaded using
% swipl [-s] file.pl
  105edit :-
  106    current_prolog_flag(associated_file, File),
  107    !,
  108    edit(file(File)).
  109edit :-
  110    '$cmd_option_val'(script_file, OsFiles),
  111    OsFiles = [OsFile],
  112    !,
  113    prolog_to_os_filename(File, OsFile),
  114    edit(file(File)).
  115edit :-
  116    throw(error(context_error(edit, no_default_file), _)).
 locations(+Spec, -Locations) is det
Locate entities matching Spec. Locations is a list of pairs Location-FullSpec, where Location is a dict holding a file, optional line and optional linepos keys. FullSpec is the disambiguated specification, e.g., member expands to member/2 for the predicate.
  126locations(Spec, Locations) :-
  127    load_extensions,
  128    findall(Location-FullSpec,
  129            locate(Spec, FullSpec, Location),
  130            Pairs0),
  131    sort(Pairs0, Pairs1),
  132    merge_locations(Pairs1, Locations).
  133
  134
  135                 /*******************************
  136                 *            LOCATE            *
  137                 *******************************/
 locate(+Spec, -FullSpec, -Location:dict)
  141locate(FileSpec:Line, file(Path, line(Line)), #{file:Path, line:Line}) :-
  142    integer(Line), Line >= 1,
  143    ground(FileSpec),                      % so specific; do not try alts
  144    !,
  145    locate(FileSpec, _, #{file:Path}).
  146locate(FileSpec:Line:LinePos,
  147       file(Path, line(Line), linepos(LinePos)),
  148       #{file:Path, line:Line, linepos:LinePos}) :-
  149    integer(Line), Line >= 1,
  150    integer(LinePos), LinePos >= 1,
  151    ground(FileSpec),                      % so specific; do not try alts
  152    !,
  153    locate(FileSpec, _, #{file:Path}).
  154locate(Path, file(Path), #{file:Path}) :-
  155    atom(Path),
  156    exists_file(Path).
  157locate(Pattern, file(Path), #{file:Path}) :-
  158    atom(Pattern),
  159    catch(expand_file_name(Pattern, Files), error(_,_), fail),
  160    member(Path, Files),
  161    exists_file(Path).
  162locate(FileBase, file(File), #{file:File}) :-
  163    atom(FileBase),
  164    find_source(FileBase, File).
  165locate(FileSpec, file(File), #{file:File}) :-
  166    is_file_search_spec(FileSpec),
  167    find_source(FileSpec, File).
  168locate(FileBase, source_file(Path),  #{file:Path}) :-
  169    atom(FileBase),
  170    source_file(Path),
  171    file_base_name(Path, File),
  172    (   File == FileBase
  173    ->  true
  174    ;   file_name_extension(FileBase, _, File)
  175    ).
  176locate(FileBase, include_file(Path),  #{file:Path}) :-
  177    atom(FileBase),
  178    setof(Path, include_file(Path), Paths),
  179    member(Path, Paths),
  180    file_base_name(Path, File),
  181    (   File == FileBase
  182    ->  true
  183    ;   file_name_extension(FileBase, _, File)
  184    ).
  185locate(Name, FullSpec, Location) :-
  186    atom(Name),
  187    locate(Name/_, FullSpec, Location).
  188locate(Name/Arity, Module:Name/Arity, Location) :-
  189    locate(Module:Name/Arity, Location).
  190locate(Name//DCGArity, FullSpec, Location) :-
  191    (   integer(DCGArity)
  192    ->  Arity is DCGArity+2,
  193        locate(Name/Arity, FullSpec, Location)
  194    ;   locate(Name/_, FullSpec, Location) % demand arity >= 2
  195    ).
  196locate(Name/Arity, library(File),  #{file:PlPath}) :-
  197    atom(Name),
  198    '$in_library'(Name, Arity, Path),
  199    (   absolute_file_name(library(.), Dir,
  200                           [ file_type(directory),
  201                             solutions(all)
  202                           ]),
  203        atom_concat(Dir, File0, Path),
  204        atom_concat(/, File, File0)
  205    ->  find_source(Path, PlPath)
  206    ;   fail
  207    ).
  208locate(Module:Name, Module:Name/Arity, Location) :-
  209    locate(Module:Name/Arity, Location).
  210locate(Module:Head, Module:Name/Arity, Location) :-
  211    callable(Head),
  212    \+ ( Head = (PName/_),
  213         atom(PName)
  214       ),
  215    functor(Head, Name, Arity),
  216    locate(Module:Name/Arity, Location).
  217locate(Spec, module(Spec), Location) :-
  218    locate(module(Spec), Location).
  219locate(Spec, Spec, Location) :-
  220    locate(Spec, Location).
  221
  222include_file(Path) :-
  223    source_file_property(Path, included_in(_,_)).
 is_file_search_spec(@Spec) is semidet
True if Spec is valid pattern for absolute_file_name/3.
  229is_file_search_spec(Spec) :-
  230    compound(Spec),
  231    compound_name_arguments(Spec, Alias, [Arg]),
  232    is_file_spec(Arg),
  233    user:file_search_path(Alias, _),
  234    !.
  235
  236is_file_spec(Name), atom(Name) => true.
  237is_file_spec(Name), string(Name) => true.
  238is_file_spec(Term), cyclic_term(Term) => fail.
  239is_file_spec(A/B) => is_file_spec(A), is_file_spec(B).
  240is_file_spec(_) => fail.
 find_source(++FileSpec, =File) is semidet
Find a source file from FileSpec. If FileSpec resolves to a .qlf file, File is the embedded `.pl` file (which may not exist).
  247find_source(FileSpec, File) :-
  248    catch(absolute_file_name(FileSpec, File0,
  249                             [ file_type(prolog),
  250                               access(read),
  251                               file_errors(fail)
  252                             ]),
  253          error(_,_), fail),
  254    prolog_source(File0, File).
  255
  256prolog_source(File0, File) :-
  257    file_name_extension(_, Ext, File0),
  258    user:prolog_file_type(Ext, qlf),
  259    !,
  260    '$qlf_module'(File0, Info),
  261    File = Info.get(file).
  262prolog_source(File, File).
 locate(+Spec, -Location)
Locate object from the specified location.
  269locate(file(File, line(Line)), #{file:File, line:Line}).
  270locate(file(File), #{file:File}).
  271locate(Module:Name/Arity, Location) :-
  272    (   atom(Name), integer(Arity)
  273    ->  functor(Head, Name, Arity)
  274    ;   Head = _                    % leave unbound
  275    ),
  276    (   (   var(Module)
  277        ;   var(Name)
  278        )
  279    ->  NonImport = true
  280    ;   NonImport = false
  281    ),
  282    current_predicate(Name, Module:Head),
  283    \+ (   NonImport == true,
  284           Module \== system,
  285           predicate_property(Module:Head, imported_from(_))
  286       ),
  287    functor(Head, Name, Arity),     % bind arity
  288    predicate_location(Module:Head, Location).
  289locate(module(Module), Location) :-
  290    atom(Module),
  291    module_property(Module, file(Path)),
  292    (   module_property(Module, line_count(Line))
  293    ->  Location = #{file:Path, line:Line}
  294    ;   Location = #{file:Path}
  295    ).
  296locate(breakpoint(Id), Location) :-
  297    integer(Id),
  298    breakpoint_property(Id, clause(Ref)),
  299    (   breakpoint_property(Id, file(File)),
  300        breakpoint_property(Id, line_count(Line))
  301    ->  Location =  #{file:File, line:Line}
  302    ;   locate(clause(Ref), Location)
  303    ).
  304locate(clause(Ref), #{file:File, line:Line}) :-
  305    clause_property(Ref, file(File)),
  306    clause_property(Ref, line_count(Line)).
  307locate(clause(Ref, _PC), #{file:File, line:Line}) :- % TBD: use clause
  308    clause_property(Ref, file(File)),
  309    clause_property(Ref, line_count(Line)).
 predicate_location(:Predicate, -Location) is nondet
Find the source location of a predicate.
Arguments:
Predicate- is a qualified head. The module may be unbound at entry. It will be bound to the actual implementation module.
  318predicate_location(Pred, #{file:File, line:Line}) :-
  319    copy_term(Pred, Pred2),
  320    distinct(Primary, primary_predicate(Pred2, Primary)),
  321    ignore(Pred = Primary),
  322    '$predicate_source_location'(Primary, File:Line).
  323
  324primary_predicate(Pred, Primary) :-
  325    (   predicate_property(Pred, imported_from(Source))
  326    ->  strip_module(Pred, _, Head),
  327        Primary = Source:Head
  328    ;   Primary = Pred
  329    ).
 addr2location(+Address, -File, -Line) is semidet
Get the File and Line for a C address.
  336addr2location(Address, File, Line) :-
  337    '$addr2line'(Address, Source),
  338    '$addr2line_location'(Source, File, Line).
  339
  340
  341                 /*******************************
  342                 *             EDIT             *
  343                 *******************************/
 do_edit_source(+Location)
Actually call the editor to edit Location, a list of Name(Value) that contains file(File) and may contain line(Line). First the multifile hook edit_source/1 is called. If this fails the system checks for XPCE and the prolog-flag editor. If the latter is built_in or pce_emacs, it will start PceEmacs.

Finally, it will get the editor to use from the prolog-flag editor and use edit_command/2 to determine how this editor should be called.

  357do_edit_source(Location) :-             % hook
  358    edit_source(Location),
  359    !.
  360do_edit_source(Location) :-             % PceEmacs
  361    current_prolog_flag(editor, Editor),
  362    is_pceemacs(Editor),
  363    current_prolog_flag(gui, true),
  364    !,
  365    location_url(Location, URL),        % File[:Line[:LinePos]]
  366    run_pce_emacs(URL).
  367do_edit_source(Location) :-             % External editor
  368    external_edit_command(Location, Command),
  369    print_message(informational, edit(waiting_for_editor)),
  370    (   catch(shell(Command), E,
  371              (print_message(warning, E),
  372               fail))
  373    ->  print_message(informational, edit(make)),
  374        make
  375    ;   print_message(informational, edit(canceled))
  376    ).
  377
  378external_edit_command(Location, Command) :-
  379    #{file:File, line:Line} :< Location,
  380    editor(Editor),
  381    file_base_name(Editor, EditorFile),
  382    file_name_extension(Base, _, EditorFile),
  383    edit_command(Base, Cmd),
  384    prolog_to_os_filename(File, OsFile),
  385    atom_codes(Cmd, S0),
  386    substitute('%e', Editor, S0, S1),
  387    substitute('%f', OsFile, S1, S2),
  388    substitute('%d', Line,   S2, S),
  389    !,
  390    atom_codes(Command, S).
  391external_edit_command(Location, Command) :-
  392    #{file:File} :< Location,
  393    editor(Editor),
  394    file_base_name(Editor, EditorFile),
  395    file_name_extension(Base, _, EditorFile),
  396    edit_command(Base, Cmd),
  397    prolog_to_os_filename(File, OsFile),
  398    atom_codes(Cmd, S0),
  399    substitute('%e', Editor, S0, S1),
  400    substitute('%f', OsFile, S1, S),
  401    \+ substitute('%d', 1, S, _),
  402    !,
  403    atom_codes(Command, S).
  404external_edit_command(Location, Command) :-
  405    #{file:File} :< Location,
  406    editor(Editor),
  407    format(string(Command), '"~w" "~w"', [Editor, File]).
  408
  409is_pceemacs(pce_emacs).
  410is_pceemacs(built_in).
 run_pce_emacs(+URL) is semidet
Dynamically load and run emacs/1.
  416run_pce_emacs(URL) :-
  417    autoload_call(in_pce_thread(autoload_call(emacs(URL)))).
 editor(-Editor)
Determine the external editor to run.
  423editor(Editor) :-                       % $EDITOR
  424    current_prolog_flag(editor, Editor),
  425    (   sub_atom(Editor, 0, _, _, $)
  426    ->  sub_atom(Editor, 1, _, 0, Var),
  427        catch(getenv(Var, Editor), _, fail), !
  428    ;   Editor == default
  429    ->  catch(getenv('EDITOR', Editor), _, fail), !
  430    ;   \+ is_pceemacs(Editor)
  431    ->  !
  432    ).
  433editor(Editor) :-                       % User defaults
  434    getenv('EDITOR', Editor),
  435    !.
  436editor(vi) :-                           % Platform defaults
  437    current_prolog_flag(unix, true),
  438    !.
  439editor(notepad) :-
  440    current_prolog_flag(windows, true),
  441    !.
  442editor(_) :-                            % No luck
  443    throw(error(existence_error(editor), _)).
 edit_command(+Editor, -Command)
This predicate should specify the shell-command called to invoke the user's editor. The following substitutions will be made:
%ePath name of the editor
%fPath name of the file to be edited
%dLine number of the target
  455edit_command(vi,          '%e +%d \'%f\'').
  456edit_command(vi,          '%e \'%f\'').
  457edit_command(emacs,       '%e +%d \'%f\'').
  458edit_command(emacs,       '%e \'%f\'').
  459edit_command(notepad,     '"%e" "%f"').
  460edit_command(wordpad,     '"%e" "%f"').
  461edit_command(uedit32,     '%e "%f/%d/0"').      % ultraedit (www.ultraedit.com)
  462edit_command(jedit,       '%e -wait \'%f\' +line:%d').
  463edit_command(jedit,       '%e -wait \'%f\'').
  464edit_command(edit,        '%e %f:%d').          % PceEmacs client script
  465edit_command(edit,        '%e %f').
  466
  467edit_command(emacsclient, Command) :- edit_command(emacs, Command).
  468edit_command(vim,         Command) :- edit_command(vi,    Command).
  469edit_command(nvim,        Command) :- edit_command(vi,    Command).
  470
  471substitute(FromAtom, ToAtom, Old, New) :-
  472    atom_codes(FromAtom, From),
  473    (   atom(ToAtom)
  474    ->  atom_codes(ToAtom, To)
  475    ;   number_codes(ToAtom, To)
  476    ),
  477    append(Pre, S0, Old),
  478    append(From, Post, S0) ->
  479    append(Pre, To, S1),
  480    append(S1, Post, New),
  481    !.
  482substitute(_, _, Old, Old).
  483
  484
  485                 /*******************************
  486                 *            SELECT            *
  487                 *******************************/
  488
  489merge_locations(Locations0, Locations) :-
  490    append(Before, [L1|Rest], Locations0),
  491    select(L2, Rest, Rest1),
  492    merge_location(L1, L2, Loc),
  493    !,
  494    append([Before, [Loc], Rest1], Locations1),
  495    merge_locations(Locations1, Locations).
  496merge_locations(Locations, Locations).
  497
  498merge_location(Loc1-Spec1, Loc2-Spec2, Loc1-Spec1) :-
  499    same_file_location(Loc1,Loc2),
  500    better_spec(Spec1, Spec2).
  501merge_location(Loc1-Spec1, Loc2-Spec2, Loc-Spec) :-
  502    same_location(Loc1, Loc2, Loc),
  503    merge_specs(Spec1, Spec2, Spec).
  504
  505same_file_location(L1, L2) :-
  506    #{file:File} :< L1,
  507    #{file:File} :< L2.
  508
  509same_location(L, L, L).
  510same_location(#{file:F1}, #{file:F2}, #{file:F}) :-
  511    best_same_file(F1, F2, F).
  512same_location(#{file:F1, line:Line}, #{file:F2}, #{file:F, line:Line}) :-
  513    best_same_file(F1, F2, F).
  514same_location(#{file:F1}, #{file:F2, line:Line}, #{file:F, line:Line}) :-
  515    best_same_file(F1, F2, F).
  516
  517best_same_file(F1, F2, F) :-
  518    catch(same_file(F1, F2), _, fail),
  519    !,
  520    atom_length(F1, L1),
  521    atom_length(F2, L2),
  522    (   L1 < L2
  523    ->  F = F1
  524    ;   F = F2
  525    ).
  526
  527merge_specs(Spec, Spec, Spec) :-
  528    !.
  529merge_specs(file(F1), file(F2), file(F)) :-
  530    best_same_file(F1, F2, F),
  531    !.
  532merge_specs(Spec1, Spec2, Spec) :-
  533    merge_specs_(Spec1, Spec2, Spec),
  534    !.
  535merge_specs(Spec1, Spec2, Spec) :-
  536    merge_specs_(Spec2, Spec1, Spec),
  537    !.
  538
  539merge_specs_(FileSpec, Spec, Spec) :-
  540    is_filespec(FileSpec).
  541
  542is_filespec(file(_)) => true.
  543is_filespec(source_file(_)) => true.
  544is_filespec(Term),
  545    compound(Term),
  546    compound_name_arguments(Term, Alias, [_Arg]),
  547    user:file_search_path(Alias, _) => true.
  548is_filespec(_) =>
  549    fail.
  550
  551better_spec(class(_), module(_)).
  552better_spec(_, FileSpec) :-
  553    is_filespec(FileSpec).
 select_location(+Pairs, +UserSpec, -Location) is semidet
Arguments:
Pairs- is a list of Location-Spec pairs
Location- is a list of properties
  560do_select_location(Pairs, Spec, Location) :-
  561    select_location(Pairs, Spec, Location),                % HOOK
  562    !,
  563    Location \== [].
  564do_select_location([], Spec, _) :-
  565    !,
  566    print_message(warning, edit(not_found(Spec))),
  567    fail.
  568do_select_location([#{file:File}-file(File)], _, Location) :-
  569    !,
  570    Location = #{file:File}.
  571do_select_location([Location-_Spec], _, Location) :-
  572    existing_location(Location),
  573    !.
  574do_select_location(Pairs, _, Location) :-
  575    foldl(number_location, Pairs, NPairs, 1, End),
  576    print_message(help, edit(select(NPairs))),
  577    (   End == 1
  578    ->  fail
  579    ;   Max is End - 1,
  580        user_selection(Max, I),
  581        memberchk(I-(Location-_Spec), NPairs)
  582    ).
 existing_location(+Location) is semidet
True when Location can be edited. By default that means that the file exists. This facility is hooked to allow for alternative ways to reach the source, e.g., by lazily downloading it.
  590existing_location(Location) :-
  591    exists_location(Location),
  592    !.
  593existing_location(Location) :-
  594    #{file:File} :< Location,
  595    access_file(File, read).
  596
  597number_location(Pair, N-Pair, N, N1) :-
  598    Pair = Location-_Spec,
  599    existing_location(Location),
  600    !,
  601    N1 is N+1.
  602number_location(Pair, 0-Pair, N, N).
  603
  604user_selection(Max, I) :-
  605    user_select(Max, I),
  606    !.
  607user_selection(Max, I) :-
  608    print_message(help, edit(choose(Max))),
  609    read_number(Max, I).
 read_number(+Max, -X) is semidet
Read a number between 1 and Max. If Max < 10, use get_single_char/1.
  615read_number(Max, X) :-
  616    Max < 10,
  617    !,
  618    get_single_char(C),
  619    put_code(user_error, C),
  620    between(0'0, 0'9, C),
  621    X is C - 0'0.
  622read_number(_, X) :-
  623    read_line_to_string(user_input, String),
  624    number_string(X, String).
  625
  626
  627                 /*******************************
  628                 *             MESSAGES         *
  629                 *******************************/
  630
  631:- multifile
  632    prolog:message/3.  633
  634prolog:message(edit(Msg)) -->
  635    message(Msg).
  636
  637message(not_found(Spec)) -->
  638    [ 'Cannot find anything to edit from "~p"'-[Spec] ],
  639    (   { atom(Spec) }
  640    ->  [ nl, '    Use edit(file(~q)) to create a new file'-[Spec] ]
  641    ;   []
  642    ).
  643message(select(NPairs)) -->
  644    { \+ (member(N-_, NPairs), N > 0) },
  645    !,
  646    [ 'Found the following locations:', nl ],
  647    sequence(target, [nl], NPairs).
  648message(select(NPairs)) -->
  649    [ 'Please select item to edit:', nl ],
  650    sequence(target, [nl], NPairs).
  651message(choose(_Max)) -->
  652    [ nl, 'Your choice? ', flush ].
  653message(waiting_for_editor) -->
  654    [ 'Waiting for editor ... ', flush ].
  655message(make) -->
  656    [ 'Running make to reload modified files' ].
  657message(canceled) -->
  658    [ 'Editor returned failure; skipped make/0 to reload files' ].
  659
  660target(0-(Location-Spec)) ==>
  661    [ ansi(warning, '~t*~3| ', [])],
  662    edit_specifier(Spec),
  663    [ '~t~32|' ],
  664    edit_location(Location, false),
  665    [ ansi(warning, ' (no source available)', [])].
  666target(N-(Location-Spec)) ==>
  667    [ ansi(bold, '~t~d~3| ', [N])],
  668    edit_specifier(Spec),
  669    [ '~t~32|' ],
  670    edit_location(Location, true).
  671
  672edit_specifier(Module:Name/Arity) ==>
  673    [ '~w:'-[Module],
  674      ansi(code, '~w/~w', [Name, Arity]) ].
  675edit_specifier(file(_Path)) ==>
  676    [ '<file>' ].
  677edit_specifier(source_file(_Path)) ==>
  678    [ '<loaded file>' ].
  679edit_specifier(include_file(_Path)) ==>
  680    [ '<included file>' ].
  681edit_specifier(Term) ==>
  682    [ '~p'-[Term] ].
  683
  684edit_location(Location, false) ==>
  685    { location_label(Location, Label) },
  686    [ ansi(warning, '~s', [Label]) ].
  687edit_location(Location, true) ==>
  688    { location_label(Location, Label),
  689      location_url(Location, URL)
  690    },
  691    [ url(URL, Label) ].
  692
  693location_label(Location, Label) :-
  694    #{file:File, line:Line} :< Location,
  695    !,
  696    short_filename(File, ShortFile),
  697    format(string(Label), '~w:~d', [ShortFile, Line]).
  698location_label(Location, Label) :-
  699    #{file:File} :< Location,
  700    !,
  701    short_filename(File, ShortFile),
  702    format(string(Label), '~w', [ShortFile]).
  703
  704location_url(Location, File:Line:LinePos) :-
  705    #{file:File, line:Line, linepos:LinePos} :< Location,
  706    !.
  707location_url(Location, File:Line) :-
  708    #{file:File, line:Line} :< Location,
  709    !.
  710location_url(Location, File) :-
  711    #{file:File} :< Location.
 short_filename(+Path, -Spec) is det
Spec is a way to refer to the file Path that is shorter. The path is shortened by either taking it relative to the current working directory or use one of the Prolog path aliases.
  719short_filename(Path, Spec) :-
  720    working_directory(Here, Here),
  721    atom_concat(Here, Local0, Path),
  722    !,
  723    remove_leading_slash(Local0, Spec).
  724short_filename(Path, Spec) :-
  725    findall(LenAlias, aliased_path(Path, LenAlias), Keyed),
  726    keysort(Keyed, [_-Spec|_]).
  727short_filename(Path, Path).
  728
  729aliased_path(Path, Len-Spec) :-
  730    setof(Alias, file_alias_path(Alias), Aliases),
  731    member(Alias, Aliases),
  732    Alias \== autoload,             % confusing and covered by something else
  733    Term =.. [Alias, '.'],
  734    absolute_file_name(Term, Prefix,
  735                       [ file_type(directory),
  736                         file_errors(fail),
  737                         solutions(all)
  738                       ]),
  739    atom_concat(Prefix, Local0, Path),
  740    remove_leading_slash(Local0, Local1),
  741    remove_extension(Local1, Local2),
  742    unquote_segments(Local2, Local),
  743    atom_length(Local2, Len),
  744    Spec =.. [Alias, Local].
  745
  746file_alias_path(Alias) :-
  747    user:file_search_path(Alias, _).
  748
  749remove_leading_slash(Path, Local) :-
  750    atom_concat(/, Local, Path),
  751    !.
  752remove_leading_slash(Path, Path).
  753
  754remove_extension(File0, File) :-
  755    file_name_extension(File, Ext, File0),
  756    user:prolog_file_type(Ext, source),
  757    !.
  758remove_extension(File, File).
  759
  760unquote_segments(File, Segments) :-
  761    split_string(File, "/", "/", SegmentStrings),
  762    maplist(atom_string, SegmentList, SegmentStrings),
  763    maplist(no_quote_needed, SegmentList),
  764    !,
  765    segments(SegmentList, Segments).
  766unquote_segments(File, File).
  767
  768
  769no_quote_needed(A) :-
  770    format(atom(Q), '~q', [A]),
  771    Q == A.
  772
  773segments([Segment], Segment) :-
  774    !.
  775segments(List, A/Segment) :-
  776    append(L1, [Segment], List),
  777    !,
  778    segments(L1, A).
  779
  780
  781                 /*******************************
  782                 *        LOAD EXTENSIONS       *
  783                 *******************************/
  784
  785load_extensions :-
  786    load,
  787    fail.
  788load_extensions.
  789
  790:- load_extensions.