575 lines
16 KiB
Prolog
Executable File
575 lines
16 KiB
Prolog
Executable File
/* Part of SWI-Prolog
|
|
|
|
Author: Jan Wielemaker
|
|
E-mail: J.Wielemaker@vu.nl
|
|
WWW: http://www.swi-prolog.org
|
|
Copyright (C): 1985-2013, University of Amsterdam
|
|
VU University Amsterdam
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
As a special exception, if you link this library with other files,
|
|
compiled with a Free Software compiler, to produce an executable, this
|
|
library does not by itself cause the resulting executable to be covered
|
|
by the GNU General Public License. This exception does not however
|
|
invalidate any other reasons why the executable file might be covered by
|
|
the GNU General Public License.
|
|
*/
|
|
|
|
:- module(prolog_edit,
|
|
[ edit/1, % +Spec
|
|
edit/0
|
|
]).
|
|
:- use_module(library(lists), [append/3, member/2, nth1/3, memberchk/2]).
|
|
:- use_module(library(maplist)).
|
|
:- use_module(library(system)).
|
|
%:- use_module(library(make), [make/0]).
|
|
:- set_prolog_flag(generate_debug_info, false).
|
|
|
|
/** <module> Editor interface
|
|
@ingroup swi
|
|
|
|
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.
|
|
*/
|
|
|
|
:- multifile
|
|
locate/3, % +Partial, -FullSpec, -Location
|
|
locate/2, % +FullSpec, -Location
|
|
select_location/3, % +Pairs, +Spec, -Location
|
|
edit_source/1, % +Location
|
|
edit_command/2, % +Editor, -Command
|
|
load/0. % provides load-hooks
|
|
|
|
%% edit(+Spec)
|
|
%
|
|
% Edit indicated object.
|
|
|
|
edit(Spec) :-
|
|
notrace(edit_no_trace(Spec)).
|
|
|
|
edit_no_trace(Spec) :-
|
|
var(Spec), !,
|
|
throw(error(instantiation_error, _)).
|
|
edit_no_trace(Spec) :-
|
|
load_extensions,
|
|
findall(Location-FullSpec,
|
|
locate(Spec, FullSpec, Location),
|
|
Pairs0),
|
|
merge_locations(Pairs0, Pairs),
|
|
do_select_location(Pairs, Spec, Location),
|
|
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
|
|
% ==
|
|
|
|
edit :-
|
|
current_prolog_flag(associated_file, File), !,
|
|
edit(file(File)).
|
|
edit :-
|
|
fail, '$cmd_option_val'(script_file, OsFiles),
|
|
OsFiles = [OsFile], !,
|
|
prolog_to_os_filename(File, OsFile),
|
|
edit(file(File)).
|
|
edit :-
|
|
throw(error(context_error(edit, no_default_file), _)).
|
|
|
|
|
|
/*******************************
|
|
* LOCATE *
|
|
*******************************/
|
|
|
|
%% locate(+Spec, -FullSpec, -Location)
|
|
|
|
locate(FileSpec:Line, file(Path, line(Line)), [file(Path), line(Line)]) :-
|
|
integer(Line), Line >= 1,
|
|
ground(FileSpec), !, % so specific; do not try alts
|
|
locate(FileSpec, _, [file(Path)]).
|
|
locate(FileSpec:Line:LinePos,
|
|
file(Path, line(Line), linepos(LinePos)),
|
|
[file(Path), line(Line), linepos(LinePos)]) :-
|
|
integer(Line), Line >= 1,
|
|
integer(LinePos), LinePos >= 1,
|
|
ground(FileSpec), !, % so specific; do not try alts
|
|
locate(FileSpec, _, [file(Path)]).
|
|
locate(Path, file(Path), [file(Path)]) :-
|
|
atom(Path),
|
|
exists_file(Path),
|
|
\+ exists_directory(Path).
|
|
locate(Pattern, file(Path), [file(Path)]) :-
|
|
atom(Pattern),
|
|
catch(expand_file_name(Pattern, Files), _, fail),
|
|
member(Path, Files),
|
|
exists_file(Path),
|
|
\+ exists_directory(Path).
|
|
locate(FileBase, file(File), [file(File)]) :-
|
|
atom(FileBase),
|
|
absolute_file_name(FileBase,
|
|
[ file_type(prolog),
|
|
access(read),
|
|
file_errors(fail)
|
|
],
|
|
File),
|
|
\+ exists_directory(File).
|
|
locate(FileSpec, file(File), [file(File)]) :-
|
|
catch(absolute_file_name(FileSpec,
|
|
[ file_type(prolog),
|
|
access(read),
|
|
file_errors(fail)
|
|
],
|
|
File),
|
|
_, fail).
|
|
locate(FileBase, source_file(Path), [file(Path)]) :-
|
|
atom(FileBase),
|
|
source_file(Path),
|
|
file_base_name(Path, File),
|
|
( File == FileBase
|
|
-> true
|
|
; file_name_extension(FileBase, _, File)
|
|
).
|
|
locate(FileBase, include_file(Path), [file(Path)]) :-
|
|
atom(FileBase),
|
|
setof(Path, include_file(Path), Paths),
|
|
member(Path, Paths),
|
|
file_base_name(Path, File),
|
|
( File == FileBase
|
|
-> true
|
|
; file_name_extension(FileBase, _, File)
|
|
).
|
|
locate(Name, FullSpec, Location) :-
|
|
atom(Name),
|
|
locate(Name/_, FullSpec, Location).
|
|
locate(Name/Arity, Module:Name/Arity, Location) :-
|
|
locate(Module:Name/Arity, Location).
|
|
locate(Name//DCGArity, FullSpec, Location) :-
|
|
( integer(DCGArity)
|
|
-> Arity is DCGArity+2,
|
|
locate(Name/Arity, FullSpec, Location)
|
|
; locate(Name/_, FullSpec, Location) % demand arity >= 2
|
|
).
|
|
locate(Name/Arity, library(File), [file(PlPath)]) :-
|
|
atom(Name),
|
|
fail, %'$in_library'(Name, Arity, Path),
|
|
( absolute_file_name(library(.),
|
|
[ file_type(directory),
|
|
solutions(all)
|
|
],
|
|
Dir),
|
|
atom_concat(Dir, File0, Path),
|
|
atom_concat(/, File, File0)
|
|
-> absolute_file_name(Path,
|
|
[ file_type(prolog),
|
|
access(read),
|
|
file_errors(fail)
|
|
],
|
|
PlPath)
|
|
; fail
|
|
).
|
|
locate(Module:Name, Module:Name/Arity, Location) :-
|
|
locate(Module:Name/Arity, Location).
|
|
locate(Module:Head, Module:Name/Arity, Location) :-
|
|
callable(Head),
|
|
functor(Head, Name, Arity),
|
|
locate(Module:Name/Arity, Location).
|
|
locate(Spec, module(Spec), Location) :-
|
|
locate(module(Spec), Location).
|
|
locate(Spec, Spec, Location) :-
|
|
locate(Spec, Location).
|
|
|
|
include_file(Path) :-
|
|
source_file_property(Path, included_in(_,_)).
|
|
|
|
|
|
%% locate(+Spec, -Location)
|
|
%
|
|
% Locate object from the specified location.
|
|
|
|
locate(file(File, line(Line)), [file(File), line(Line)]).
|
|
locate(file(File), [file(File)]).
|
|
locate(Module:Name/Arity, [file(File), line(Line)]) :-
|
|
( atom(Name), integer(Arity)
|
|
-> functor(Head, Name, Arity)
|
|
; Head = _ % leave unbound
|
|
),
|
|
( ( var(Module)
|
|
; var(Name)
|
|
)
|
|
-> NonImport = true
|
|
; NonImport = false
|
|
),
|
|
current_predicate(Name, Module:Head),
|
|
\+ ( NonImport == true,
|
|
Module \== system,
|
|
predicate_property(Module:Head, imported_from(_))
|
|
),
|
|
functor(Head, Name, Arity), % bind arity
|
|
predicate_property(Module:Head, file(File)),
|
|
predicate_property(Module:Head, line_count(Line)).
|
|
locate(module(Module), [file(Path)|Rest]) :-
|
|
atom(Module),
|
|
module_property(Module, file(Path)),
|
|
( module_property(Module, line_count(Line))
|
|
-> Rest = [line(Line)]
|
|
; Rest = []
|
|
).
|
|
locate(breakpoint(Id), Location) :-
|
|
integer(Id),
|
|
breakpoint_property(Id, clause(Ref)),
|
|
( breakpoint_property(Id, file(File)),
|
|
breakpoint_property(Id, line_count(Line))
|
|
-> Location = [file(File),line(Line)]
|
|
; locate(clause(Ref), Location)
|
|
).
|
|
locate(clause(Ref), [file(File), line(Line)]) :-
|
|
clause_property(Ref, file(File)),
|
|
clause_property(Ref, line_count(Line)).
|
|
locate(clause(Ref, _PC), [file(File), line(Line)]) :- % TBD: use clause
|
|
clause_property(Ref, file(File)),
|
|
clause_property(Ref, line_count(Line)).
|
|
|
|
|
|
/*******************************
|
|
* EDIT *
|
|
*******************************/
|
|
|
|
%% 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.
|
|
|
|
do_edit_source(Location) :- % hook
|
|
edit_source(Location), !.
|
|
do_edit_source(Location) :- % PceEmacs
|
|
current_prolog_flag(editor, Editor),
|
|
pceemacs(Editor),
|
|
current_prolog_flag(gui, true), !,
|
|
memberchk(file(File), Location),
|
|
( memberchk(line(Line), Location)
|
|
-> ( memberchk(linepos(LinePos), Location)
|
|
-> Pos = (File:Line:LinePos)
|
|
; Pos = (File:Line)
|
|
)
|
|
; Pos = File
|
|
),
|
|
in_pce_thread(emacs(Pos)).
|
|
do_edit_source(Location) :- % External editor
|
|
external_edit_command(Location, Command),
|
|
print_message(informational, edit(waiting_for_editor)),
|
|
( catch(system(Command), E,
|
|
(print_message(warning, E),
|
|
fail))
|
|
-> print_message(informational, edit(make)),
|
|
make
|
|
; print_message(informational, edit(canceled))
|
|
).
|
|
|
|
external_edit_command(Location, Command) :-
|
|
memberchk(file(File), Location),
|
|
memberchk(line(Line), Location),
|
|
editor(Editor),
|
|
file_base_name(Editor, EditorFile),
|
|
file_name_extension(Base, _, EditorFile),
|
|
edit_command(Base, Cmd),
|
|
prolog_to_os_filename(File, OsFile),
|
|
atom_codes(Cmd, S0),
|
|
substitute('%e', Editor, S0, S1),
|
|
substitute('%f', OsFile, S1, S2),
|
|
substitute('%d', Line, S2, S), !,
|
|
atom_codes(Command, S).
|
|
external_edit_command(Location, Command) :-
|
|
memberchk(file(File), Location),
|
|
editor(Editor),
|
|
file_base_name(Editor, EditorFile),
|
|
file_name_extension(Base, _, EditorFile),
|
|
edit_command(Base, Cmd),
|
|
prolog_to_os_filename(File, OsFile),
|
|
atom_codes(Cmd, S0),
|
|
substitute('%e', Editor, S0, S1),
|
|
substitute('%f', OsFile, S1, S),
|
|
\+ substitute('%d', 1, S, _), !,
|
|
atom_codes(Command, S).
|
|
external_edit_command(Location, Command) :-
|
|
memberchk(file(File), Location),
|
|
editor(Editor),
|
|
atomic_list_concat(['"', Editor, '" "', File, '"'], Command).
|
|
|
|
pceemacs(pce_emacs).
|
|
pceemacs(built_in).
|
|
|
|
%% editor(-Editor)
|
|
%
|
|
% Determine the external editor to run.
|
|
|
|
editor(Editor) :- % $EDITOR
|
|
current_prolog_flag(editor, Editor),
|
|
( sub_atom(Editor, 0, _, _, $)
|
|
-> sub_atom(Editor, 1, _, 0, Var),
|
|
catch(getenv(Var, Editor), _, fail), !
|
|
; Editor == default
|
|
-> catch(getenv('EDITOR', Editor), _, fail), !
|
|
; \+ pceemacs(Editor)
|
|
-> !
|
|
).
|
|
editor(Editor) :- % User defaults
|
|
getenv('EDITOR', Editor), !.
|
|
editor(vi) :- % Platform defaults
|
|
current_prolog_flag(unix, true), !.
|
|
editor(notepad) :-
|
|
current_prolog_flag(windows, true), !.
|
|
editor(_) :- % No luck
|
|
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:
|
|
%
|
|
% | %e | Path name of the editor |
|
|
% | %f | Path name of the file to be edited |
|
|
% | %d | Line number of the target |
|
|
|
|
|
|
edit_command(vi, '%e +%d \'%f\'').
|
|
edit_command(vi, '%e \'%f\'').
|
|
edit_command(emacs, '%e +%d \'%f\'').
|
|
edit_command(emacs, '%e \'%f\'').
|
|
edit_command(notepad, '"%e" "%f"').
|
|
edit_command(wordpad, '"%e" "%f"').
|
|
edit_command(uedit32, '%e "%f/%d/0"'). % ultraedit (www.ultraedit.com)
|
|
edit_command(jedit, '%e -wait \'%f\' +line:%d').
|
|
edit_command(jedit, '%e -wait \'%f\'').
|
|
edit_command(edit, '%e %f:%d'). % PceEmacs client script
|
|
edit_command(edit, '%e %f').
|
|
|
|
edit_command(emacsclient, Command) :- edit_command(emacs, Command).
|
|
edit_command(vim, Command) :- edit_command(vi, Command).
|
|
|
|
substitute(FromAtom, ToAtom, Old, New) :-
|
|
atom_codes(FromAtom, From),
|
|
( atom(ToAtom)
|
|
-> atom_codes(ToAtom, To)
|
|
; number_codes(ToAtom, To)
|
|
),
|
|
append(Pre, S0, Old),
|
|
append(From, Post, S0) ->
|
|
append(Pre, To, S1),
|
|
append(S1, Post, New), !.
|
|
substitute(_, _, Old, Old).
|
|
|
|
|
|
/*******************************
|
|
* SELECT *
|
|
*******************************/
|
|
|
|
merge_locations(Pairs0, Pairs) :-
|
|
keysort(Pairs0, Pairs1),
|
|
merge_locations2(Pairs1, Pairs).
|
|
|
|
merge_locations2([], []).
|
|
merge_locations2([H0|T0], [H|T]) :-
|
|
remove_same_location(H0, H, T0, T1),
|
|
merge_locations2(T1, T).
|
|
|
|
remove_same_location(Pair0, H, [Pair1|T0], L) :-
|
|
merge_locations(Pair0, Pair1, Pair2), !,
|
|
remove_same_location(Pair2, H, T0, L).
|
|
remove_same_location(H, H, L, L).
|
|
|
|
merge_locations(Loc1-Spec1, Loc2-Spec2, Loc-Spec) :-
|
|
same_location(Loc1, Loc2, Loc), !,
|
|
( merge_specs(Spec1, Spec2, Spec)
|
|
; merge_specs(Spec2, Spec1, Spec)
|
|
; Spec = Spec1
|
|
), !.
|
|
merge_locations([file(X)]-_, Loc-Spec, Loc-Spec) :-
|
|
memberchk(file(X), Loc),
|
|
memberchk(line(_), Loc).
|
|
|
|
same_location(L, L, L).
|
|
same_location([file(F1)], [file(F2)], [file(F)]) :-
|
|
best_same_file(F1, F2, F).
|
|
same_location([file(F1),line(L)], [file(F2)], [file(F),line(L)]) :-
|
|
best_same_file(F1, F2, F).
|
|
same_location([file(F1)], [file(F2),line(L)], [file(F),line(L)]) :-
|
|
best_same_file(F1, F2, F).
|
|
|
|
best_same_file(F1, F2, F) :-
|
|
catch(same_file(F1, F2), _, fail), !,
|
|
atom_length(F1, L1),
|
|
atom_length(F2, L2),
|
|
( L1 < L2
|
|
-> F = F1
|
|
; F = F2
|
|
).
|
|
|
|
merge_specs(source_file(Path), _, source_file(Path)).
|
|
|
|
%% select_location(+Pairs, +UserSpec, -Location)
|
|
|
|
do_select_location(Pairs, Spec, Location) :-
|
|
select_location(Pairs, Spec, Location), !, % HOOK
|
|
Location \== [].
|
|
do_select_location([], Spec, _) :- !,
|
|
print_message(warning, edit(not_found(Spec))),
|
|
fail.
|
|
do_select_location([Location-_Spec], _, Location) :- !.
|
|
do_select_location(Pairs, _, Location) :-
|
|
print_message(help, edit(select)),
|
|
list_pairs(Pairs, 0, N),
|
|
print_message(help, edit(prompt_select)),
|
|
read_number(N, I),
|
|
nth1(I, Pairs, Location-_Spec), !.
|
|
|
|
list_pairs([], N, N).
|
|
list_pairs([H|T], N0, N) :-
|
|
NN is N0 + 1,
|
|
list_pair(H, NN),
|
|
list_pairs(T, NN, N).
|
|
|
|
list_pair(Pair, N) :-
|
|
print_message(help, edit(target(Pair, N))).
|
|
|
|
|
|
read_number(Max, X) :-
|
|
Max < 10, !,
|
|
get_single_char(C),
|
|
between(0'0, 0'9, C),
|
|
X is C - 0'0.
|
|
read_number(_, X) :-
|
|
read_line(Chars),
|
|
name(X, Chars),
|
|
integer(X).
|
|
|
|
read_line(Chars) :-
|
|
get0(user_input, C0),
|
|
read_line(C0, Chars).
|
|
|
|
read_line(10, []) :- !.
|
|
read_line(-1, []) :- !.
|
|
read_line(C, [C|T]) :-
|
|
get0(user_input, C1),
|
|
read_line(C1, T).
|
|
|
|
|
|
/*******************************
|
|
* MESSAGES *
|
|
*******************************/
|
|
|
|
:- multifile
|
|
prolog:message/3.
|
|
|
|
prolog:message(edit(not_found(Spec))) -->
|
|
[ 'Cannot find anything to edit from "~p"'-[Spec] ],
|
|
( { atom(Spec) }
|
|
-> [ nl, ' Use edit(file(~q)) to create a new file'-[Spec] ]
|
|
; []
|
|
).
|
|
prolog:message(edit(select)) -->
|
|
[ 'Please select item to edit:', nl, nl ].
|
|
prolog:message(edit(prompt_select)) -->
|
|
[ nl, 'Your choice? ', flush ].
|
|
prolog:message(edit(target(Location-Spec, N))) -->
|
|
[ '~t~d~3| '-[N]],
|
|
edit_specifier(Spec),
|
|
[ '~t~32|' ],
|
|
edit_location(Location).
|
|
prolog:message(edit(waiting_for_editor)) -->
|
|
[ 'Waiting for editor ... ', flush ].
|
|
prolog:message(edit(make)) -->
|
|
[ 'Running make to reload modified files' ].
|
|
prolog:message(edit(canceled)) -->
|
|
[ 'Editor returned failure; skipped make/0 to reload files' ].
|
|
|
|
edit_specifier(Module:Name/Arity) --> !,
|
|
[ '~w:~w/~w'-[Module, Name, Arity] ].
|
|
edit_specifier(file(_Path)) --> !,
|
|
[ '<file>' ].
|
|
edit_specifier(source_file(_Path)) --> !,
|
|
[ '<loaded file>' ].
|
|
edit_specifier(include_file(_Path)) --> !,
|
|
[ '<included file>' ].
|
|
edit_specifier(Term) -->
|
|
[ '~p'-[Term] ].
|
|
|
|
edit_location(Location) -->
|
|
{ memberchk(file(File), Location),
|
|
memberchk(line(Line), Location),
|
|
short_filename(File, Spec)
|
|
}, !,
|
|
[ '~q:~d'-[Spec, Line] ].
|
|
edit_location(Location) -->
|
|
{ memberchk(file(File), Location),
|
|
short_filename(File, Spec)
|
|
}, !,
|
|
[ '~q'-[Spec] ].
|
|
|
|
short_filename(Path, Spec) :-
|
|
absolute_file_name('', Here),
|
|
atom_concat(Here, Local0, Path), !,
|
|
remove_leading_slash(Local0, Spec).
|
|
short_filename(Path, Spec) :-
|
|
findall(LenAlias, aliased_path(Path, LenAlias), Keyed),
|
|
keysort(Keyed, [_-Spec|_]).
|
|
short_filename(Path, Path).
|
|
|
|
aliased_path(Path, Len-Spec) :-
|
|
setof(Alias, file_alias_path(Alias), Aliases),
|
|
member(Alias, Aliases),
|
|
Alias \== autoload, % confusing and covered by something else
|
|
Term =.. [Alias, '.'],
|
|
absolute_file_name(Term,
|
|
[ file_type(directory),
|
|
file_errors(fail),
|
|
solutions(all)
|
|
], Prefix),
|
|
atom_concat(Prefix, Local0, Path),
|
|
remove_leading_slash(Local0, Local),
|
|
atom_length(Local, Len),
|
|
Spec =.. [Alias, Local].
|
|
|
|
file_alias_path(Alias) :-
|
|
user:file_search_path(Alias, _).
|
|
|
|
remove_leading_slash(Path, Local) :-
|
|
atom_concat(/, Local, Path), !.
|
|
remove_leading_slash(Path, Path).
|
|
|
|
|
|
/*******************************
|
|
* LOAD EXTENSIONS *
|
|
*******************************/
|
|
|
|
load_extensions :-
|
|
load,
|
|
fail.
|
|
load_extensions.
|
|
|
|
:- load_extensions.
|