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(dcg/basics), [string/3, integer/3, remainder/3]).   48:- autoload(library(solution_sequences), [distinct/2]).   49
   50
   51% :- 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. */

   61:- multifile
   62    locate/3,                       % +Partial, -FullSpec, -Location
   63    locate/2,                       % +FullSpec, -Location
   64    select_location/3,              % +Pairs, +Spec, -Location
   65    exists_location/1,              % +Location
   66    user_select/2,                  % +Max, -I
   67    edit_source/1,                  % +Location
   68    edit_command/2,                 % +Editor, -Command
   69    load/0.                         % provides load-hooks
   70
   71:- public
   72    predicate_location/2,           % :Pred, -Location
   73    addr2location/3.                % +Address, -File, -Line
 edit(+Spec)
Edit indicated object.
   79edit(Spec) :-
   80    notrace(edit_no_trace(Spec)).
   81
   82edit_no_trace(Spec) :-
   83    var(Spec),
   84    !,
   85    throw(error(instantiation_error, _)).
   86edit_no_trace(Spec) :-
   87    load_extensions,
   88    findall(Location-FullSpec,
   89            locate(Spec, FullSpec, Location),
   90            Pairs0),
   91    sort(Pairs0, Pairs1),
   92    merge_locations(Pairs1, 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), _)).
  117
  118
  119                 /*******************************
  120                 *            LOCATE            *
  121                 *******************************/
 locate(+Spec, -FullSpec, -Location:dict)
  125locate(FileSpec:Line, file(Path, line(Line)), #{file:Path, line:Line}) :-
  126    integer(Line), Line >= 1,
  127    ground(FileSpec),                      % so specific; do not try alts
  128    !,
  129    locate(FileSpec, _, #{file:Path}).
  130locate(FileSpec:Line:LinePos,
  131       file(Path, line(Line), linepos(LinePos)),
  132       #{file:Path, line:Line, linepos:LinePos}) :-
  133    integer(Line), Line >= 1,
  134    integer(LinePos), LinePos >= 1,
  135    ground(FileSpec),                      % so specific; do not try alts
  136    !,
  137    locate(FileSpec, _, #{file:Path}).
  138locate(Path, file(Path), #{file:Path}) :-
  139    atom(Path),
  140    exists_file(Path).
  141locate(Pattern, file(Path), #{file:Path}) :-
  142    atom(Pattern),
  143    catch(expand_file_name(Pattern, Files), error(_,_), fail),
  144    member(Path, Files),
  145    exists_file(Path).
  146locate(FileBase, file(File), #{file:File}) :-
  147    atom(FileBase),
  148    find_source(FileBase, File).
  149locate(FileSpec, file(File), #{file:File}) :-
  150    is_file_search_spec(FileSpec),
  151    find_source(FileSpec, File).
  152locate(FileBase, source_file(Path),  #{file:Path}) :-
  153    atom(FileBase),
  154    source_file(Path),
  155    file_base_name(Path, File),
  156    (   File == FileBase
  157    ->  true
  158    ;   file_name_extension(FileBase, _, File)
  159    ).
  160locate(FileBase, include_file(Path),  #{file:Path}) :-
  161    atom(FileBase),
  162    setof(Path, include_file(Path), Paths),
  163    member(Path, Paths),
  164    file_base_name(Path, File),
  165    (   File == FileBase
  166    ->  true
  167    ;   file_name_extension(FileBase, _, File)
  168    ).
  169locate(Name, FullSpec, Location) :-
  170    atom(Name),
  171    locate(Name/_, FullSpec, Location).
  172locate(Name/Arity, Module:Name/Arity, Location) :-
  173    locate(Module:Name/Arity, Location).
  174locate(Name//DCGArity, FullSpec, Location) :-
  175    (   integer(DCGArity)
  176    ->  Arity is DCGArity+2,
  177        locate(Name/Arity, FullSpec, Location)
  178    ;   locate(Name/_, FullSpec, Location) % demand arity >= 2
  179    ).
  180locate(Name/Arity, library(File),  #{file:PlPath}) :-
  181    atom(Name),
  182    '$in_library'(Name, Arity, Path),
  183    (   absolute_file_name(library(.), Dir,
  184                           [ file_type(directory),
  185                             solutions(all)
  186                           ]),
  187        atom_concat(Dir, File0, Path),
  188        atom_concat(/, File, File0)
  189    ->  find_source(Path, PlPath)
  190    ;   fail
  191    ).
  192locate(Module:Name, Module:Name/Arity, Location) :-
  193    locate(Module:Name/Arity, Location).
  194locate(Module:Head, Module:Name/Arity, Location) :-
  195    callable(Head),
  196    \+ ( Head = (PName/_),
  197         atom(PName)
  198       ),
  199    functor(Head, Name, Arity),
  200    locate(Module:Name/Arity, Location).
  201locate(Spec, module(Spec), Location) :-
  202    locate(module(Spec), Location).
  203locate(Spec, Spec, Location) :-
  204    locate(Spec, Location).
  205
  206include_file(Path) :-
  207    source_file_property(Path, included_in(_,_)).
 is_file_search_spec(@Spec) is semidet
True if Spec is valid pattern for absolute_file_name/3.
  213is_file_search_spec(Spec) :-
  214    compound(Spec),
  215    compound_name_arguments(Spec, Alias, [Arg]),
  216    is_file_spec(Arg),
  217    user:file_search_path(Alias, _),
  218    !.
  219
  220is_file_spec(Name), atom(Name) => true.
  221is_file_spec(Name), string(Name) => true.
  222is_file_spec(Term), cyclic_term(Term) => fail.
  223is_file_spec(A/B) => is_file_spec(A), is_file_spec(B).
  224is_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).
  231find_source(FileSpec, File) :-
  232    catch(absolute_file_name(FileSpec, File0,
  233                             [ file_type(prolog),
  234                               access(read),
  235                               file_errors(fail)
  236                             ]),
  237          error(_,_), fail),
  238    prolog_source(File0, File).
  239
  240prolog_source(File0, File) :-
  241    file_name_extension(_, Ext, File0),
  242    user:prolog_file_type(Ext, qlf),
  243    !,
  244    '$qlf_module'(File0, Info),
  245    File = Info.get(file).
  246prolog_source(File, File).
 locate(+Spec, -Location)
Locate object from the specified location.
  253locate(file(File, line(Line)), #{file:File, line:Line}).
  254locate(file(File), #{file:File}).
  255locate(Module:Name/Arity, Location) :-
  256    (   atom(Name), integer(Arity)
  257    ->  functor(Head, Name, Arity)
  258    ;   Head = _                    % leave unbound
  259    ),
  260    (   (   var(Module)
  261        ;   var(Name)
  262        )
  263    ->  NonImport = true
  264    ;   NonImport = false
  265    ),
  266    current_predicate(Name, Module:Head),
  267    \+ (   NonImport == true,
  268           Module \== system,
  269           predicate_property(Module:Head, imported_from(_))
  270       ),
  271    functor(Head, Name, Arity),     % bind arity
  272    predicate_location(Module:Head, Location).
  273locate(module(Module), Location) :-
  274    atom(Module),
  275    module_property(Module, file(Path)),
  276    (   module_property(Module, line_count(Line))
  277    ->  Location = #{file:Path, line:Line}
  278    ;   Location = #{file:Path}
  279    ).
  280locate(breakpoint(Id), Location) :-
  281    integer(Id),
  282    breakpoint_property(Id, clause(Ref)),
  283    (   breakpoint_property(Id, file(File)),
  284        breakpoint_property(Id, line_count(Line))
  285    ->  Location =  #{file:File, line:Line}
  286    ;   locate(clause(Ref), Location)
  287    ).
  288locate(clause(Ref), #{file:File, line:Line}) :-
  289    clause_property(Ref, file(File)),
  290    clause_property(Ref, line_count(Line)).
  291locate(clause(Ref, _PC), #{file:File, line:Line}) :- % TBD: use clause
  292    clause_property(Ref, file(File)),
  293    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.
  302predicate_location(Pred, #{file:File, line:Line}) :-
  303    copy_term(Pred, Pred2),
  304    distinct(Primary, primary_predicate(Pred2, Primary)),
  305    ignore(Pred = Primary),
  306    (   predicate_property(Primary, file(File)),
  307        predicate_property(Primary, line_count(Line))
  308    ->  true
  309    ;   '$foreign_predicate_source'(Primary, Source),
  310        string_codes(Source, Codes),
  311        phrase(addr2line_output(File, Line), Codes)
  312    ).
  313
  314primary_predicate(Pred, Primary) :-
  315    (   predicate_property(Pred, imported_from(Source))
  316    ->  strip_module(Pred, _, Head),
  317        Primary = Source:Head
  318    ;   Primary = Pred
  319    ).
 addr2location(+Address, -File, -Line) is semidet
Get the File and Line for a C address.
  326addr2location(Address, File, Line) :-
  327    '$addr2line'(Address, Source),
  328    string_codes(Source, Codes),
  329    phrase(addr2line_output(File, Line), Codes).
 addr2line_output(-File, -Line)// is semidet
Process the output of the addr2line utility. This implementation works for Linux. Additional lines may be needed for other environments.
  337addr2line_output(File, Line) -->
  338    string(_), " at ", string(FileCodes), ":", integer(Line),
  339    !,
  340    remainder(_),
  341    { atom_codes(File, FileCodes) }.
  342
  343
  344                 /*******************************
  345                 *             EDIT             *
  346                 *******************************/
 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.

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