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:- if(exists_source(library(editline))).   49:- autoload(library(editline), [el_history_events/2]).   50:- endif.   51
   52% :- 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.

   62:- multifile
   63    locate/3,                       % +Partial, -FullSpec, -Location
   64    locate/2,                       % +FullSpec, -Location
   65    select_location/3,              % +Pairs, +Spec, -Location
   66    exists_location/1,              % +Location
   67    user_select/2,                  % +Max, -I
   68    edit_source/1,                  % +Location
   69    edit_command/2,                 % +Editor, -Command
   70    load/0.                         % provides load-hooks
   71
   72:- public
   73    locations/2,                    % +Spec, -Locations
   74    predicate_location/2,           % :Pred, -Location
   75    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.
   86edit(Spec) :-
   87    notrace(edit_no_trace(Spec)).
   88
   89edit_no_trace(Spec) :-
   90    var(Spec),
   91    !,
   92    throw(error(instantiation_error, _)).
   93edit_no_trace(Spec) :-
   94    locations(Spec, Pairs),
   95    do_select_location(Pairs, Spec, Location),
   96    do_edit_source(Location).
 edit
Edit associated or script file. This is the Prolog file opened by double-clicking, the initial file loaded from the commandline using e.g., the command below or the last file loaded into the current session using ?- [file]., ?- consult(file). or ?- use_module(file).
% swipl [-s] file.pl
  110edit :-
  111    current_prolog_flag(associated_file, File),
  112    !,
  113    edit(file(File)).
  114edit :-
  115    '$cmd_option_val'(script_file, OsFiles),
  116    OsFiles = [OsFile],
  117    !,
  118    prolog_to_os_filename(File, OsFile),
  119    edit(file(File)).
  120:- if(exists_source(library(editline))).  121edit :-
  122    session_loaded_file(File),
  123    !,
  124    edit(file(File)).
  125:- endif.  126edit :-
  127    throw(error(context_error(edit, no_default_file), _)).
  128
  129:- if(exists_source(library(editline))).  130session_loaded_file(File) :-
  131    catch(el_history_events(user_input, Events),
  132          error(_,_), fail),
  133    member(_-Event, Events),
  134    catch(term_string(Command, Event), error(_,_), fail),
  135    load_command(Command, File0),
  136    (   is_list(File0)
  137    ->  member(File1, File0)
  138    ;   File1 = File0
  139    ),
  140    catch(absolute_file_name(File1, File,
  141                             [ file_type(source),
  142                               file_errors(fail)
  143                             ]),
  144          error(_,_), fail),
  145    source_file(File),
  146    !.
  147
  148load_command([File|T], [File|T]).
  149load_command(consult(File), File).
  150load_command(use_module(File), File).
  151:- endif.
 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.
  161locations(Spec, Locations) :-
  162    load_extensions,
  163    findall(Location-FullSpec,
  164            locate(Spec, FullSpec, Location),
  165            Pairs0),
  166    sort(Pairs0, Pairs1),
  167    merge_locations(Pairs1, Locations).
  168
  169
  170                 /*******************************
  171                 *            LOCATE            *
  172                 *******************************/
 locate(+Spec, -FullSpec, -Location:dict)
  176locate(FileSpec:Line, file(Path, line(Line)), #{file:Path, line:Line}) :-
  177    integer(Line), Line >= 1,
  178    ground(FileSpec),                      % so specific; do not try alts
  179    !,
  180    locate(FileSpec, _, #{file:Path}).
  181locate(FileSpec:Line:LinePos,
  182       file(Path, line(Line), linepos(LinePos)),
  183       #{file:Path, line:Line, linepos:LinePos}) :-
  184    integer(Line), Line >= 1,
  185    integer(LinePos), LinePos >= 1,
  186    ground(FileSpec),                      % so specific; do not try alts
  187    !,
  188    locate(FileSpec, _, #{file:Path}).
  189locate(Path, file(Path), #{file:Path}) :-
  190    atom(Path),
  191    exists_file(Path).
  192locate(Pattern, file(Path), #{file:Path}) :-
  193    atom(Pattern),
  194    catch(expand_file_name(Pattern, Files), error(_,_), fail),
  195    member(Path, Files),
  196    exists_file(Path).
  197locate(FileBase, file(File), #{file:File}) :-
  198    atom(FileBase),
  199    find_source(FileBase, File).
  200locate(FileSpec, file(File), #{file:File}) :-
  201    is_file_search_spec(FileSpec),
  202    find_source(FileSpec, File).
  203locate(FileBase, source_file(Path),  #{file:Path}) :-
  204    atom(FileBase),
  205    source_file(Path),
  206    file_base_name(Path, File),
  207    (   File == FileBase
  208    ->  true
  209    ;   file_name_extension(FileBase, _, File)
  210    ).
  211locate(FileBase, include_file(Path),  #{file:Path}) :-
  212    atom(FileBase),
  213    setof(Path, include_file(Path), Paths),
  214    member(Path, Paths),
  215    file_base_name(Path, File),
  216    (   File == FileBase
  217    ->  true
  218    ;   file_name_extension(FileBase, _, File)
  219    ).
  220locate(Name, FullSpec, Location) :-
  221    atom(Name),
  222    locate(Name/_, FullSpec, Location).
  223locate(Name/Arity, Module:Name/Arity, Location) :-
  224    locate(Module:Name/Arity, Location).
  225locate(Name//DCGArity, FullSpec, Location) :-
  226    (   integer(DCGArity)
  227    ->  Arity is DCGArity+2,
  228        locate(Name/Arity, FullSpec, Location)
  229    ;   locate(Name/_, FullSpec, Location) % demand arity >= 2
  230    ).
  231locate(Name/Arity, library(File),  #{file:PlPath}) :-
  232    atom(Name),
  233    '$in_library'(Name, Arity, Path),
  234    (   absolute_file_name(library(.), Dir,
  235                           [ file_type(directory),
  236                             solutions(all)
  237                           ]),
  238        atom_concat(Dir, File0, Path),
  239        atom_concat(/, File, File0)
  240    ->  find_source(Path, PlPath)
  241    ;   fail
  242    ).
  243locate(Module:Name, Module:Name/Arity, Location) :-
  244    locate(Module:Name/Arity, Location).
  245locate(Module:Head, Module:Name/Arity, Location) :-
  246    callable(Head),
  247    \+ ( Head = (PName/_),
  248         atom(PName)
  249       ),
  250    functor(Head, Name, Arity),
  251    locate(Module:Name/Arity, Location).
  252locate(Spec, module(Spec), Location) :-
  253    locate(module(Spec), Location).
  254locate(Spec, Spec, Location) :-
  255    locate(Spec, Location).
  256
  257include_file(Path) :-
  258    source_file_property(Path, included_in(_,_)).
 is_file_search_spec(@Spec) is semidet
True if Spec is valid pattern for absolute_file_name/3.
  264is_file_search_spec(Spec) :-
  265    compound(Spec),
  266    compound_name_arguments(Spec, Alias, [Arg]),
  267    is_file_spec(Arg),
  268    user:file_search_path(Alias, _),
  269    !.
  270
  271is_file_spec(Name), atom(Name) => true.
  272is_file_spec(Name), string(Name) => true.
  273is_file_spec(Term), cyclic_term(Term) => fail.
  274is_file_spec(A/B) => is_file_spec(A), is_file_spec(B).
  275is_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).
  282find_source(FileSpec, File) :-
  283    catch(absolute_file_name(FileSpec, File0,
  284                             [ file_type(prolog),
  285                               access(read),
  286                               file_errors(fail)
  287                             ]),
  288          error(_,_), fail),
  289    prolog_source(File0, File).
  290
  291prolog_source(File0, File) :-
  292    file_name_extension(_, Ext, File0),
  293    user:prolog_file_type(Ext, qlf),
  294    !,
  295    '$qlf_module'(File0, Info),
  296    File = Info.get(file).
  297prolog_source(File, File).
 locate(+Spec, -Location)
Locate object from the specified location.
  304locate(file(File, line(Line)), #{file:File, line:Line}).
  305locate(file(File), #{file:File}).
  306locate(Module:Name/Arity, Location) :-
  307    (   atom(Name), integer(Arity)
  308    ->  functor(Head, Name, Arity)
  309    ;   Head = _                    % leave unbound
  310    ),
  311    (   (   var(Module)
  312        ;   var(Name)
  313        )
  314    ->  NonImport = true
  315    ;   NonImport = false
  316    ),
  317    current_predicate(Name, Module:Head),
  318    \+ (   NonImport == true,
  319           Module \== system,
  320           predicate_property(Module:Head, imported_from(_))
  321       ),
  322    functor(Head, Name, Arity),     % bind arity
  323    predicate_location(Module:Head, Location).
  324locate(module(Module), Location) :-
  325    atom(Module),
  326    module_property(Module, file(Path)),
  327    (   module_property(Module, line_count(Line))
  328    ->  Location = #{file:Path, line:Line}
  329    ;   Location = #{file:Path}
  330    ).
  331locate(breakpoint(Id), Location) :-
  332    integer(Id),
  333    breakpoint_property(Id, clause(Ref)),
  334    (   breakpoint_property(Id, file(File)),
  335        breakpoint_property(Id, line_count(Line))
  336    ->  Location =  #{file:File, line:Line}
  337    ;   locate(clause(Ref), Location)
  338    ).
  339locate(clause(Ref), #{file:File, line:Line}) :-
  340    clause_property(Ref, file(File)),
  341    clause_property(Ref, line_count(Line)).
  342locate(clause(Ref, _PC), #{file:File, line:Line}) :- % TBD: use clause
  343    clause_property(Ref, file(File)),
  344    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.
  353predicate_location(Pred, #{file:File, line:Line}) :-
  354    copy_term(Pred, Pred2),
  355    distinct(Primary, primary_predicate(Pred2, Primary)),
  356    ignore(Pred = Primary),
  357    '$predicate_source_location'(Primary, File:Line).
  358
  359primary_predicate(Pred, Primary) :-
  360    (   predicate_property(Pred, imported_from(Source))
  361    ->  strip_module(Pred, _, Head),
  362        Primary = Source:Head
  363    ;   Primary = Pred
  364    ).
 addr2location(+Address, -File, -Line) is semidet
Get the File and Line for a C address.
  371addr2location(Address, File, Line) :-
  372    '$addr2line'(Address, Source),
  373    '$addr2line_location'(Source, File, Line).
  374
  375
  376                 /*******************************
  377                 *             EDIT             *
  378                 *******************************/
 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.

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