View source with formatted 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).
   51
   52/** <module> Editor interface
   53
   54This module implements the generic editor  interface. It consists of two
   55extensible parts with little  in  between.   The  first  part deals with
   56translating the input into source-location, and the second with starting
   57an editor.
   58*/
   59
   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
   74
   75%!  edit(+Spec)
   76%
   77%   Edit indicated object.  Spec  is  a   predicate  indicator,  a  file
   78%   specification or a source location   `File:Line` or `File:Line:Col`.
   79%   Both Line and Col count from 1,  as   in  the messages we print, the
   80%   hyperlinks we emit (see ansi_hyperlink/2) and the messages of e.g.,
   81%   the C compiler.  Note that  the   `line_position`  of a stream (see
   82%   stream_position_data/3) counts from 0.
   83
   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).
   95
   96%!  edit
   97%
   98%   Edit associated or script file.  This is the Prolog file opened
   99%   by double-clicking or the file loaded using
  100%
  101%     ==
  102%     % swipl [-s] file.pl
  103%     ==
  104
  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%!  locations(+Spec, -Locations) is det.
  119%
  120%   Locate  entities  matching  Spec.  Locations  is  a  list  of  pairs
  121%   `Location-FullSpec`, where `Location` is a   dict  holding a `file`,
  122%   optional `line` and  optional  `linepos`   keys.  `FullSpec`  is the
  123%   disambiguated specification, e.g., `member`   expands  to `member/2`
  124%   for the predicate.
  125
  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                 *******************************/
  138
  139%!  locate(+Spec, -FullSpec, -Location:dict)
  140
  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(_,_)).
  224
  225%!  is_file_search_spec(@Spec) is semidet.
  226%
  227%   True if Spec is valid pattern for absolute_file_name/3.
  228
  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.
  241
  242%!  find_source(++FileSpec, =File) is semidet.
  243%
  244%   Find a source file from FileSpec.  If FileSpec resolves to a .qlf
  245%   file, File is the embedded `.pl` file (which may not exist).
  246
  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).
  263
  264
  265%!  locate(+Spec, -Location)
  266%
  267%   Locate object from the specified location.
  268
  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)).
  310
  311%!  predicate_location(:Predicate, -Location) is nondet.
  312%
  313%   Find the source location of a predicate.
  314%
  315%   @arg Predicate is a qualified head.  The   module  may be unbound at
  316%   entry. It will be bound to the actual implementation module.
  317
  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    ).
  330
  331
  332%!  addr2location(+Address, -File, -Line) is semidet.
  333%
  334%   Get the File and Line for a C address.
  335
  336addr2location(Address, File, Line) :-
  337    '$addr2line'(Address, Source),
  338    '$addr2line_location'(Source, File, Line).
  339
  340
  341                 /*******************************
  342                 *             EDIT             *
  343                 *******************************/
  344
  345%!  do_edit_source(+Location)
  346%
  347%   Actually call the editor to edit Location, a list of Name(Value)
  348%   that contains file(File) and may contain line(Line). First the
  349%   multifile hook edit_source/1 is called. If this fails the system
  350%   checks for XPCE and the prolog-flag editor. If the latter is
  351%   built_in or pce_emacs, it will start PceEmacs.
  352%
  353%   Finally, it will get the editor to use from the prolog-flag
  354%   editor and use edit_command/2 to determine how this editor
  355%   should be called.
  356
  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).
  411
  412%!  run_pce_emacs(+URL) is semidet.
  413%
  414%   Dynamically load and run emacs/1.
  415
  416run_pce_emacs(URL) :-
  417    autoload_call(in_pce_thread(autoload_call(emacs(URL)))).
  418
  419%!  editor(-Editor)
  420%
  421%   Determine the external editor to run.
  422
  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), _)).
  444
  445%!  edit_command(+Editor, -Command)
  446%
  447%   This predicate should specify the shell-command called to invoke
  448%   the user's editor. The following substitutions will be made:
  449%
  450%           | %e | Path name of the editor            |
  451%           | %f | Path name of the file to be edited |
  452%           | %d | Line number of the target          |
  453
  454
  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).
  554
  555%!  select_location(+Pairs, +UserSpec, -Location) is semidet.
  556%
  557%   @arg Pairs is a list of `Location-Spec` pairs
  558%   @arg Location is a list of properties
  559
  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    ).
  583
  584%!  existing_location(+Location) is semidet.
  585%
  586%   True when Location can be edited.  By   default  that means that the
  587%   file exists. This facility is hooked   to allow for alternative ways
  588%   to reach the source, e.g., by lazily downloading it.
  589
  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).
  610
  611%!  read_number(+Max, -X) is semidet.
  612%
  613%   Read a number between 1 and Max. If Max < 10, use get_single_char/1.
  614
  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.
  712
  713%!  short_filename(+Path, -Spec) is det.
  714%
  715%   Spec is a way to refer to the file Path that is shorter. The path is
  716%   shortened by either taking  it  relative   to  the  current  working
  717%   directory or use one of the Prolog path aliases.
  718
  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.