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

   52:- multifile
   53    locate/3,                       % +Partial, -FullSpec, -Location
   54    locate/2,                       % +FullSpec, -Location
   55    select_location/3,              % +Pairs, +Spec, -Location
   56    edit_source/1,                  % +Location
   57    edit_command/2,                 % +Editor, -Command
   58    load/0.                         % provides load-hooks
 edit(+Spec)
Edit indicated object.
   64edit(Spec) :-
   65    notrace(edit_no_trace(Spec)).
   66
   67edit_no_trace(Spec) :-
   68    var(Spec),
   69    !,
   70    throw(error(instantiation_error, _)).
   71edit_no_trace(Spec) :-
   72    load_extensions,
   73    findall(Location-FullSpec,
   74            locate(Spec, FullSpec, Location),
   75            Pairs0),
   76    merge_locations(Pairs0, Pairs),
   77    do_select_location(Pairs, Spec, Location),
   78    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
   89edit :-
   90    current_prolog_flag(associated_file, File),
   91    !,
   92    edit(file(File)).
   93edit :-
   94    '$cmd_option_val'(script_file, OsFiles),
   95    OsFiles = [OsFile],
   96    !,
   97    prolog_to_os_filename(File, OsFile),
   98    edit(file(File)).
   99edit :-
  100    throw(error(context_error(edit, no_default_file), _)).
  101
  102
  103                 /*******************************
  104                 *            LOCATE            *
  105                 *******************************/
 locate(+Spec, -FullSpec, -Location)
  109locate(FileSpec:Line, file(Path, line(Line)), [file(Path), line(Line)]) :-
  110    integer(Line), Line >= 1,
  111    ground(FileSpec),                      % so specific; do not try alts
  112    !,
  113    locate(FileSpec, _, [file(Path)]).
  114locate(FileSpec:Line:LinePos,
  115       file(Path, line(Line), linepos(LinePos)),
  116       [file(Path), line(Line), linepos(LinePos)]) :-
  117    integer(Line), Line >= 1,
  118    integer(LinePos), LinePos >= 1,
  119    ground(FileSpec),                      % so specific; do not try alts
  120    !,
  121    locate(FileSpec, _, [file(Path)]).
  122locate(Path, file(Path), [file(Path)]) :-
  123    atom(Path),
  124    exists_file(Path),
  125    \+ exists_directory(Path).
  126locate(Pattern, file(Path), [file(Path)]) :-
  127    atom(Pattern),
  128    catch(expand_file_name(Pattern, Files), _, fail),
  129    member(Path, Files),
  130    exists_file(Path),
  131    \+ exists_directory(Path).
  132locate(FileBase, file(File), [file(File)]) :-
  133    atom(FileBase),
  134    absolute_file_name(FileBase,
  135                       [ file_type(prolog),
  136                         access(read),
  137                         file_errors(fail)
  138                       ],
  139                       File),
  140    \+ exists_directory(File).
  141locate(FileSpec, file(File), [file(File)]) :-
  142    catch(absolute_file_name(FileSpec,
  143                             [ file_type(prolog),
  144                               access(read),
  145                               file_errors(fail)
  146                             ],
  147                             File),
  148          _, fail).
  149locate(FileBase, source_file(Path), [file(Path)]) :-
  150    atom(FileBase),
  151    source_file(Path),
  152    file_base_name(Path, File),
  153    (   File == FileBase
  154    ->  true
  155    ;   file_name_extension(FileBase, _, File)
  156    ).
  157locate(FileBase, include_file(Path), [file(Path)]) :-
  158    atom(FileBase),
  159    setof(Path, include_file(Path), Paths),
  160    member(Path, Paths),
  161    file_base_name(Path, File),
  162    (   File == FileBase
  163    ->  true
  164    ;   file_name_extension(FileBase, _, File)
  165    ).
  166locate(Name, FullSpec, Location) :-
  167    atom(Name),
  168    locate(Name/_, FullSpec, Location).
  169locate(Name/Arity, Module:Name/Arity, Location) :-
  170    locate(Module:Name/Arity, Location).
  171locate(Name//DCGArity, FullSpec, Location) :-
  172    (   integer(DCGArity)
  173    ->  Arity is DCGArity+2,
  174        locate(Name/Arity, FullSpec, Location)
  175    ;   locate(Name/_, FullSpec, Location) % demand arity >= 2
  176    ).
  177locate(Name/Arity, library(File), [file(PlPath)]) :-
  178    atom(Name),
  179    '$in_library'(Name, Arity, Path),
  180    (   absolute_file_name(library(.),
  181                           [ file_type(directory),
  182                             solutions(all)
  183                           ],
  184                           Dir),
  185        atom_concat(Dir, File0, Path),
  186        atom_concat(/, File, File0)
  187    ->  absolute_file_name(Path,
  188                           [ file_type(prolog),
  189                             access(read),
  190                             file_errors(fail)
  191                           ],
  192                           PlPath)
  193    ;   fail
  194    ).
  195locate(Module:Name, Module:Name/Arity, Location) :-
  196    locate(Module:Name/Arity, Location).
  197locate(Module:Head, Module:Name/Arity, Location) :-
  198    callable(Head),
  199    \+ ( Head = (PName/_),
  200         atom(PName)
  201       ),
  202    functor(Head, Name, Arity),
  203    locate(Module:Name/Arity, Location).
  204locate(Spec, module(Spec), Location) :-
  205    locate(module(Spec), Location).
  206locate(Spec, Spec, Location) :-
  207    locate(Spec, Location).
  208
  209include_file(Path) :-
  210    source_file_property(Path, included_in(_,_)).
 locate(+Spec, -Location)
Locate object from the specified location.
  217locate(file(File, line(Line)), [file(File), line(Line)]).
  218locate(file(File), [file(File)]).
  219locate(Module:Name/Arity, [file(File), line(Line)]) :-
  220    (   atom(Name), integer(Arity)
  221    ->  functor(Head, Name, Arity)
  222    ;   Head = _                    % leave unbound
  223    ),
  224    (   (   var(Module)
  225        ;   var(Name)
  226        )
  227    ->  NonImport = true
  228    ;   NonImport = false
  229    ),
  230    current_predicate(Name, Module:Head),
  231    \+ (   NonImport == true,
  232           Module \== system,
  233           predicate_property(Module:Head, imported_from(_))
  234       ),
  235    functor(Head, Name, Arity),     % bind arity
  236    predicate_property(Module:Head, file(File)),
  237    predicate_property(Module:Head, line_count(Line)).
  238locate(module(Module), [file(Path)|Rest]) :-
  239    atom(Module),
  240    module_property(Module, file(Path)),
  241    (   module_property(Module, line_count(Line))
  242    ->  Rest = [line(Line)]
  243    ;   Rest = []
  244    ).
  245locate(breakpoint(Id), Location) :-
  246    integer(Id),
  247    breakpoint_property(Id, clause(Ref)),
  248    (   breakpoint_property(Id, file(File)),
  249        breakpoint_property(Id, line_count(Line))
  250    ->  Location = [file(File),line(Line)]
  251    ;   locate(clause(Ref), Location)
  252    ).
  253locate(clause(Ref), [file(File), line(Line)]) :-
  254    clause_property(Ref, file(File)),
  255    clause_property(Ref, line_count(Line)).
  256locate(clause(Ref, _PC), [file(File), line(Line)]) :- % TBD: use clause
  257    clause_property(Ref, file(File)),
  258    clause_property(Ref, line_count(Line)).
  259
  260
  261                 /*******************************
  262                 *             EDIT             *
  263                 *******************************/
 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.

  277do_edit_source(Location) :-             % hook
  278    edit_source(Location),
  279    !.
  280do_edit_source(Location) :-             % PceEmacs
  281    current_prolog_flag(editor, Editor),
  282    pceemacs(Editor),
  283    current_prolog_flag(gui, true),
  284    !,
  285    memberchk(file(File), Location),
  286    (   memberchk(line(Line), Location)
  287    ->  (   memberchk(linepos(LinePos), Location)
  288        ->  Pos = (File:Line:LinePos)
  289        ;   Pos = (File:Line)
  290        )
  291    ;   Pos = File
  292    ),
  293    in_pce_thread(emacs(Pos)).
  294do_edit_source(Location) :-             % External editor
  295    external_edit_command(Location, Command),
  296    print_message(informational, edit(waiting_for_editor)),
  297    (   catch(shell(Command), E,
  298              (print_message(warning, E),
  299               fail))
  300    ->  print_message(informational, edit(make)),
  301        make
  302    ;   print_message(informational, edit(canceled))
  303    ).
  304
  305external_edit_command(Location, Command) :-
  306    memberchk(file(File), Location),
  307    memberchk(line(Line), Location),
  308    editor(Editor),
  309    file_base_name(Editor, EditorFile),
  310    file_name_extension(Base, _, EditorFile),
  311    edit_command(Base, Cmd),
  312    prolog_to_os_filename(File, OsFile),
  313    atom_codes(Cmd, S0),
  314    substitute('%e', Editor, S0, S1),
  315    substitute('%f', OsFile, S1, S2),
  316    substitute('%d', Line,   S2, S),
  317    !,
  318    atom_codes(Command, S).
  319external_edit_command(Location, Command) :-
  320    memberchk(file(File), Location),
  321    editor(Editor),
  322    file_base_name(Editor, EditorFile),
  323    file_name_extension(Base, _, EditorFile),
  324    edit_command(Base, Cmd),
  325    prolog_to_os_filename(File, OsFile),
  326    atom_codes(Cmd, S0),
  327    substitute('%e', Editor, S0, S1),
  328    substitute('%f', OsFile, S1, S),
  329    \+ substitute('%d', 1, S, _),
  330    !,
  331    atom_codes(Command, S).
  332external_edit_command(Location, Command) :-
  333    memberchk(file(File), Location),
  334    editor(Editor),
  335    atomic_list_concat(['"', Editor, '" "', File, '"'], Command).
  336
  337pceemacs(pce_emacs).
  338pceemacs(built_in).
 editor(-Editor)
Determine the external editor to run.
  344editor(Editor) :-                       % $EDITOR
  345    current_prolog_flag(editor, Editor),
  346    (   sub_atom(Editor, 0, _, _, $)
  347    ->  sub_atom(Editor, 1, _, 0, Var),
  348        catch(getenv(Var, Editor), _, fail), !
  349    ;   Editor == default
  350    ->  catch(getenv('EDITOR', Editor), _, fail), !
  351    ;   \+ pceemacs(Editor)
  352    ->  !
  353    ).
  354editor(Editor) :-                       % User defaults
  355    getenv('EDITOR', Editor),
  356    !.
  357editor(vi) :-                           % Platform defaults
  358    current_prolog_flag(unix, true),
  359    !.
  360editor(notepad) :-
  361    current_prolog_flag(windows, true),
  362    !.
  363editor(_) :-                            % No luck
  364    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
  376edit_command(vi,          '%e +%d \'%f\'').
  377edit_command(vi,          '%e \'%f\'').
  378edit_command(emacs,       '%e +%d \'%f\'').
  379edit_command(emacs,       '%e \'%f\'').
  380edit_command(notepad,     '"%e" "%f"').
  381edit_command(wordpad,     '"%e" "%f"').
  382edit_command(uedit32,     '%e "%f/%d/0"').      % ultraedit (www.ultraedit.com)
  383edit_command(jedit,       '%e -wait \'%f\' +line:%d').
  384edit_command(jedit,       '%e -wait \'%f\'').
  385edit_command(edit,        '%e %f:%d').          % PceEmacs client script
  386edit_command(edit,        '%e %f').
  387
  388edit_command(emacsclient, Command) :- edit_command(emacs, Command).
  389edit_command(vim,         Command) :- edit_command(vi,    Command).
  390edit_command(nvim,        Command) :- edit_command(vi,    Command).
  391
  392substitute(FromAtom, ToAtom, Old, New) :-
  393    atom_codes(FromAtom, From),
  394    (   atom(ToAtom)
  395    ->  atom_codes(ToAtom, To)
  396    ;   number_codes(ToAtom, To)
  397    ),
  398    append(Pre, S0, Old),
  399    append(From, Post, S0) ->
  400    append(Pre, To, S1),
  401    append(S1, Post, New),
  402    !.
  403substitute(_, _, Old, Old).
  404
  405
  406                 /*******************************
  407                 *            SELECT            *
  408                 *******************************/
  409
  410merge_locations(Pairs0, Pairs) :-
  411    keysort(Pairs0, Pairs1),
  412    merge_locations2(Pairs1, Pairs).
  413
  414merge_locations2([], []).
  415merge_locations2([H0|T0], [H|T]) :-
  416    remove_same_location(H0, H, T0, T1),
  417    merge_locations2(T1, T).
  418
  419remove_same_location(Pair0, H, [Pair1|T0], L) :-
  420    merge_locations(Pair0, Pair1, Pair2),
  421    !,
  422    remove_same_location(Pair2, H, T0, L).
  423remove_same_location(H, H, L, L).
  424
  425merge_locations(Loc1-Spec1, Loc2-Spec2, Loc-Spec) :-
  426    same_location(Loc1, Loc2, Loc),
  427    !,
  428    (   merge_specs(Spec1, Spec2, Spec)
  429    ;   merge_specs(Spec2, Spec1, Spec)
  430    ;   Spec = Spec1
  431    ),
  432    !.
  433merge_locations([file(X)]-_, Loc-Spec, Loc-Spec) :-
  434    memberchk(file(X), Loc),
  435    memberchk(line(_), Loc).
  436
  437same_location(L, L, L).
  438same_location([file(F1)], [file(F2)], [file(F)]) :-
  439    best_same_file(F1, F2, F).
  440same_location([file(F1),line(L)], [file(F2)], [file(F),line(L)]) :-
  441    best_same_file(F1, F2, F).
  442same_location([file(F1)], [file(F2),line(L)], [file(F),line(L)]) :-
  443    best_same_file(F1, F2, F).
  444
  445best_same_file(F1, F2, F) :-
  446    catch(same_file(F1, F2), _, fail),
  447    !,
  448    atom_length(F1, L1),
  449    atom_length(F2, L2),
  450    (   L1 < L2
  451    ->  F = F1
  452    ;   F = F2
  453    ).
  454
  455merge_specs(source_file(Path), _, source_file(Path)).
 select_location(+Pairs, +UserSpec, -Location)
  459do_select_location(Pairs, Spec, Location) :-
  460    select_location(Pairs, Spec, Location),                % HOOK
  461    !,
  462    Location \== [].
  463do_select_location([], Spec, _) :-
  464    !,
  465    print_message(warning, edit(not_found(Spec))),
  466    fail.
  467do_select_location([Location-_Spec], _, Location) :- !.
  468do_select_location(Pairs, _, Location) :-
  469    print_message(help, edit(select)),
  470    list_pairs(Pairs, 0, N),
  471    print_message(help, edit(prompt_select)),
  472    read_number(N, I),
  473    nth1(I, Pairs, Location-_Spec),
  474    !.
  475
  476list_pairs([], N, N).
  477list_pairs([H|T], N0, N) :-
  478    NN is N0 + 1,
  479    list_pair(H, NN),
  480    list_pairs(T, NN, N).
  481
  482list_pair(Pair, N) :-
  483    print_message(help, edit(target(Pair, N))).
  484
  485
  486read_number(Max, X) :-
  487    Max < 10,
  488    !,
  489    get_single_char(C),
  490    between(0'0, 0'9, C),
  491    X is C - 0'0.
  492read_number(_, X) :-
  493    read_line(Chars),
  494    name(X, Chars),
  495    integer(X).
  496
  497read_line(Chars) :-
  498    get0(user_input, C0),
  499    read_line(C0, Chars).
  500
  501read_line(10, []) :- !.
  502read_line(-1, []) :- !.
  503read_line(C, [C|T]) :-
  504    get0(user_input, C1),
  505    read_line(C1, T).
  506
  507
  508                 /*******************************
  509                 *             MESSAGES         *
  510                 *******************************/
  511
  512:- multifile
  513    prolog:message/3.  514
  515prolog:message(edit(not_found(Spec))) -->
  516    [ 'Cannot find anything to edit from "~p"'-[Spec] ],
  517    (   { atom(Spec) }
  518    ->  [ nl, '    Use edit(file(~q)) to create a new file'-[Spec] ]
  519    ;   []
  520    ).
  521prolog:message(edit(select)) -->
  522    [ 'Please select item to edit:', nl, nl ].
  523prolog:message(edit(prompt_select)) -->
  524    [ nl, 'Your choice? ', flush ].
  525prolog:message(edit(target(Location-Spec, N))) -->
  526    [ '~t~d~3| '-[N]],
  527    edit_specifier(Spec),
  528    [ '~t~32|' ],
  529    edit_location(Location).
  530prolog:message(edit(waiting_for_editor)) -->
  531    [ 'Waiting for editor ... ', flush ].
  532prolog:message(edit(make)) -->
  533    [ 'Running make to reload modified files' ].
  534prolog:message(edit(canceled)) -->
  535    [ 'Editor returned failure; skipped make/0 to reload files' ].
  536
  537edit_specifier(Module:Name/Arity) -->
  538    !,
  539    [ '~w:~w/~w'-[Module, Name, Arity] ].
  540edit_specifier(file(_Path)) -->
  541    !,
  542    [ '<file>' ].
  543edit_specifier(source_file(_Path)) -->
  544    !,
  545    [ '<loaded file>' ].
  546edit_specifier(include_file(_Path)) -->
  547    !,
  548    [ '<included file>' ].
  549edit_specifier(Term) -->
  550    [ '~p'-[Term] ].
  551
  552edit_location(Location) -->
  553    { memberchk(file(File), Location),
  554      memberchk(line(Line), Location),
  555      short_filename(File, Spec)
  556    },
  557    !,
  558    [ '~q:~d'-[Spec, Line] ].
  559edit_location(Location) -->
  560    { memberchk(file(File), Location),
  561      short_filename(File, Spec)
  562    },
  563    !,
  564    [ '~q'-[Spec] ].
  565
  566short_filename(Path, Spec) :-
  567    absolute_file_name('', Here),
  568    atom_concat(Here, Local0, Path),
  569    !,
  570    remove_leading_slash(Local0, Spec).
  571short_filename(Path, Spec) :-
  572    findall(LenAlias, aliased_path(Path, LenAlias), Keyed),
  573    keysort(Keyed, [_-Spec|_]).
  574short_filename(Path, Path).
  575
  576aliased_path(Path, Len-Spec) :-
  577    setof(Alias, file_alias_path(Alias), Aliases),
  578    member(Alias, Aliases),
  579    Alias \== autoload,             % confusing and covered by something else
  580    Term =.. [Alias, '.'],
  581    absolute_file_name(Term,
  582                       [ file_type(directory),
  583                         file_errors(fail),
  584                         solutions(all)
  585                       ], Prefix),
  586    atom_concat(Prefix, Local0, Path),
  587    remove_leading_slash(Local0, Local),
  588    atom_length(Local, Len),
  589    Spec =.. [Alias, Local].
  590
  591file_alias_path(Alias) :-
  592    user:file_search_path(Alias, _).
  593
  594remove_leading_slash(Path, Local) :-
  595    atom_concat(/, Local, Path),
  596    !.
  597remove_leading_slash(Path, Path).
  598
  599
  600                 /*******************************
  601                 *        LOAD EXTENSIONS       *
  602                 *******************************/
  603
  604load_extensions :-
  605    load,
  606    fail.
  607load_extensions.
  608
  609:- load_extensions.