36
37:- module(prolog_edit,
38 [ edit/1, 39 edit/0
40 ]). 41:- autoload(library(lists), [member/2, append/3, select/3, append/2]). 42:- autoload(library(make), [make/0]). 43:- autoload(library(prolog_breakpoints), [breakpoint_property/2]). 44:- autoload(library(apply), [foldl/5, maplist/3, maplist/2]). 45:- use_module(library(dcg/high_order), [sequence/5]). 46:- autoload(library(readutil), [read_line_to_string/2]). 47:- autoload(library(dcg/basics), [string/3, integer/3, remainder/3]). 48:- autoload(library(solution_sequences), [distinct/2]). 49
50
52
60
61:- multifile
62 locate/3, 63 locate/2, 64 select_location/3, 65 exists_location/1, 66 user_select/2, 67 edit_source/1, 68 edit_command/2, 69 load/0. 70
71:- public
72 predicate_location/2, 73 addr2location/3. 74
78
79edit(Spec) :-
80 notrace(edit_no_trace(Spec)).
81
82edit_no_trace(Spec) :-
83 var(Spec),
84 !,
85 throw(error(instantiation_error, _)).
86edit_no_trace(Spec) :-
87 load_extensions,
88 findall(Location-FullSpec,
89 locate(Spec, FullSpec, Location),
90 Pairs0),
91 sort(Pairs0, Pairs1),
92 merge_locations(Pairs1, Pairs),
93 do_select_location(Pairs, Spec, Location),
94 do_edit_source(Location).
95
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
119 122
124
125locate(FileSpec:Line, file(Path, line(Line)), #{file:Path, line:Line}) :-
126 integer(Line), Line >= 1,
127 ground(FileSpec), 128 !,
129 locate(FileSpec, _, #{file:Path}).
130locate(FileSpec:Line:LinePos,
131 file(Path, line(Line), linepos(LinePos)),
132 #{file:Path, line:Line, linepos:LinePos}) :-
133 integer(Line), Line >= 1,
134 integer(LinePos), LinePos >= 1,
135 ground(FileSpec), 136 !,
137 locate(FileSpec, _, #{file:Path}).
138locate(Path, file(Path), #{file:Path}) :-
139 atom(Path),
140 exists_file(Path).
141locate(Pattern, file(Path), #{file:Path}) :-
142 atom(Pattern),
143 catch(expand_file_name(Pattern, Files), error(_,_), fail),
144 member(Path, Files),
145 exists_file(Path).
146locate(FileBase, file(File), #{file:File}) :-
147 atom(FileBase),
148 find_source(FileBase, File).
149locate(FileSpec, file(File), #{file:File}) :-
150 is_file_search_spec(FileSpec),
151 find_source(FileSpec, File).
152locate(FileBase, source_file(Path), #{file:Path}) :-
153 atom(FileBase),
154 source_file(Path),
155 file_base_name(Path, File),
156 ( File == FileBase
157 -> true
158 ; file_name_extension(FileBase, _, File)
159 ).
160locate(FileBase, include_file(Path), #{file:Path}) :-
161 atom(FileBase),
162 setof(Path, include_file(Path), Paths),
163 member(Path, Paths),
164 file_base_name(Path, File),
165 ( File == FileBase
166 -> true
167 ; file_name_extension(FileBase, _, File)
168 ).
169locate(Name, FullSpec, Location) :-
170 atom(Name),
171 locate(Name/_, FullSpec, Location).
172locate(Name/Arity, Module:Name/Arity, Location) :-
173 locate(Module:Name/Arity, Location).
174locate(Name//DCGArity, FullSpec, Location) :-
175 ( integer(DCGArity)
176 -> Arity is DCGArity+2,
177 locate(Name/Arity, FullSpec, Location)
178 ; locate(Name/_, FullSpec, Location) 179 ).
180locate(Name/Arity, library(File), #{file:PlPath}) :-
181 atom(Name),
182 '$in_library'(Name, Arity, Path),
183 ( absolute_file_name(library(.), Dir,
184 [ file_type(directory),
185 solutions(all)
186 ]),
187 atom_concat(Dir, File0, Path),
188 atom_concat(/, File, File0)
189 -> find_source(Path, PlPath)
190 ; fail
191 ).
192locate(Module:Name, Module:Name/Arity, Location) :-
193 locate(Module:Name/Arity, Location).
194locate(Module:Head, Module:Name/Arity, Location) :-
195 callable(Head),
196 \+ ( Head = (PName/_),
197 atom(PName)
198 ),
199 functor(Head, Name, Arity),
200 locate(Module:Name/Arity, Location).
201locate(Spec, module(Spec), Location) :-
202 locate(module(Spec), Location).
203locate(Spec, Spec, Location) :-
204 locate(Spec, Location).
205
206include_file(Path) :-
207 source_file_property(Path, included_in(_,_)).
208
212
213is_file_search_spec(Spec) :-
214 compound(Spec),
215 compound_name_arguments(Spec, Alias, [Arg]),
216 is_file_spec(Arg),
217 user:file_search_path(Alias, _),
218 !.
219
220is_file_spec(Name), atom(Name) => true.
221is_file_spec(Name), string(Name) => true.
222is_file_spec(Term), cyclic_term(Term) => fail.
223is_file_spec(A/B) => is_file_spec(A), is_file_spec(B).
224is_file_spec(_) => fail.
225
230
231find_source(FileSpec, File) :-
232 catch(absolute_file_name(FileSpec, File0,
233 [ file_type(prolog),
234 access(read),
235 file_errors(fail)
236 ]),
237 error(_,_), fail),
238 prolog_source(File0, File).
239
240prolog_source(File0, File) :-
241 file_name_extension(_, Ext, File0),
242 user:prolog_file_type(Ext, qlf),
243 !,
244 '$qlf_module'(File0, Info),
245 File = Info.get(file).
246prolog_source(File, File).
247
248
252
253locate(file(File, line(Line)), #{file:File, line:Line}).
254locate(file(File), #{file:File}).
255locate(Module:Name/Arity, Location) :-
256 ( atom(Name), integer(Arity)
257 -> functor(Head, Name, Arity)
258 ; Head = _ 259 ),
260 ( ( var(Module)
261 ; var(Name)
262 )
263 -> NonImport = true
264 ; NonImport = false
265 ),
266 current_predicate(Name, Module:Head),
267 \+ ( NonImport == true,
268 Module \== system,
269 predicate_property(Module:Head, imported_from(_))
270 ),
271 functor(Head, Name, Arity), 272 predicate_location(Module:Head, Location).
273locate(module(Module), Location) :-
274 atom(Module),
275 module_property(Module, file(Path)),
276 ( module_property(Module, line_count(Line))
277 -> Location = #{file:Path, line:Line}
278 ; Location = #{file:Path}
279 ).
280locate(breakpoint(Id), Location) :-
281 integer(Id),
282 breakpoint_property(Id, clause(Ref)),
283 ( breakpoint_property(Id, file(File)),
284 breakpoint_property(Id, line_count(Line))
285 -> Location = #{file:File, line:Line}
286 ; locate(clause(Ref), Location)
287 ).
288locate(clause(Ref), #{file:File, line:Line}) :-
289 clause_property(Ref, file(File)),
290 clause_property(Ref, line_count(Line)).
291locate(clause(Ref, _PC), #{file:File, line:Line}) :- 292 clause_property(Ref, file(File)),
293 clause_property(Ref, line_count(Line)).
294
301
302predicate_location(Pred, #{file:File, line:Line}) :-
303 copy_term(Pred, Pred2),
304 distinct(Primary, primary_predicate(Pred2, Primary)),
305 ignore(Pred = Primary),
306 ( predicate_property(Primary, file(File)),
307 predicate_property(Primary, line_count(Line))
308 -> true
309 ; '$foreign_predicate_source'(Primary, Source),
310 string_codes(Source, Codes),
311 phrase(addr2line_output(File, Line), Codes)
312 ).
313
314primary_predicate(Pred, Primary) :-
315 ( predicate_property(Pred, imported_from(Source))
316 -> strip_module(Pred, _, Head),
317 Primary = Source:Head
318 ; Primary = Pred
319 ).
320
321
325
326addr2location(Address, File, Line) :-
327 '$addr2line'(Address, Source),
328 string_codes(Source, Codes),
329 phrase(addr2line_output(File, Line), Codes).
330
336
337addr2line_output(File, Line) -->
338 string(_), " at ", string(FileCodes), ":", integer(Line),
339 !,
340 remainder(_),
341 { atom_codes(File, FileCodes) }.
342
343
344 347
359
360do_edit_source(Location) :- 361 edit_source(Location),
362 !.
363do_edit_source(Location) :- 364 current_prolog_flag(editor, Editor),
365 is_pceemacs(Editor),
366 current_prolog_flag(gui, true),
367 !,
368 location_url(Location, URL), 369 run_pce_emacs(URL).
370do_edit_source(Location) :- 371 external_edit_command(Location, Command),
372 print_message(informational, edit(waiting_for_editor)),
373 ( catch(shell(Command), E,
374 (print_message(warning, E),
375 fail))
376 -> print_message(informational, edit(make)),
377 make
378 ; print_message(informational, edit(canceled))
379 ).
380
381external_edit_command(Location, Command) :-
382 #{file:File, line:Line} :< Location,
383 editor(Editor),
384 file_base_name(Editor, EditorFile),
385 file_name_extension(Base, _, EditorFile),
386 edit_command(Base, Cmd),
387 prolog_to_os_filename(File, OsFile),
388 atom_codes(Cmd, S0),
389 substitute('%e', Editor, S0, S1),
390 substitute('%f', OsFile, S1, S2),
391 substitute('%d', Line, S2, S),
392 !,
393 atom_codes(Command, S).
394external_edit_command(Location, Command) :-
395 #{file:File} :< Location,
396 editor(Editor),
397 file_base_name(Editor, EditorFile),
398 file_name_extension(Base, _, EditorFile),
399 edit_command(Base, Cmd),
400 prolog_to_os_filename(File, OsFile),
401 atom_codes(Cmd, S0),
402 substitute('%e', Editor, S0, S1),
403 substitute('%f', OsFile, S1, S),
404 \+ substitute('%d', 1, S, _),
405 !,
406 atom_codes(Command, S).
407external_edit_command(Location, Command) :-
408 #{file:File} :< Location,
409 editor(Editor),
410 format(string(Command), '"~w" "~w"', [Editor, File]).
411
412is_pceemacs(pce_emacs).
413is_pceemacs(built_in).
414
418
419run_pce_emacs(URL) :-
420 autoload_call(in_pce_thread(autoload_call(emacs(URL)))).
421
425
426editor(Editor) :- 427 current_prolog_flag(editor, Editor),
428 ( sub_atom(Editor, 0, _, _, $)
429 -> sub_atom(Editor, 1, _, 0, Var),
430 catch(getenv(Var, Editor), _, fail), !
431 ; Editor == default
432 -> catch(getenv('EDITOR', Editor), _, fail), !
433 ; \+ is_pceemacs(Editor)
434 -> !
435 ).
436editor(Editor) :- 437 getenv('EDITOR', Editor),
438 !.
439editor(vi) :- 440 current_prolog_flag(unix, true),
441 !.
442editor(notepad) :-
443 current_prolog_flag(windows, true),
444 !.
445editor(_) :- 446 throw(error(existence_error(editor), _)).
447
456
457
458edit_command(vi, '%e +%d \'%f\'').
459edit_command(vi, '%e \'%f\'').
460edit_command(emacs, '%e +%d \'%f\'').
461edit_command(emacs, '%e \'%f\'').
462edit_command(notepad, '"%e" "%f"').
463edit_command(wordpad, '"%e" "%f"').
464edit_command(uedit32, '%e "%f/%d/0"'). 465edit_command(jedit, '%e -wait \'%f\' +line:%d').
466edit_command(jedit, '%e -wait \'%f\'').
467edit_command(edit, '%e %f:%d'). 468edit_command(edit, '%e %f').
469
470edit_command(emacsclient, Command) :- edit_command(emacs, Command).
471edit_command(vim, Command) :- edit_command(vi, Command).
472edit_command(nvim, Command) :- edit_command(vi, Command).
473
474substitute(FromAtom, ToAtom, Old, New) :-
475 atom_codes(FromAtom, From),
476 ( atom(ToAtom)
477 -> atom_codes(ToAtom, To)
478 ; number_codes(ToAtom, To)
479 ),
480 append(Pre, S0, Old),
481 append(From, Post, S0) ->
482 append(Pre, To, S1),
483 append(S1, Post, New),
484 !.
485substitute(_, _, Old, Old).
486
487
488 491
492merge_locations(Locations0, Locations) :-
493 append(Before, [L1|Rest], Locations0),
494 select(L2, Rest, Rest1),
495 merge_location(L1, L2, Loc),
496 !,
497 append([Before, [Loc], Rest1], Locations1),
498 merge_locations(Locations1, Locations).
499merge_locations(Locations, Locations).
500
501merge_location(Loc1-Spec1, Loc2-Spec2, Loc1-Spec1) :-
502 same_file_location(Loc1,Loc2),
503 better_spec(Spec1, Spec2).
504merge_location(Loc1-Spec1, Loc2-Spec2, Loc-Spec) :-
505 same_location(Loc1, Loc2, Loc),
506 merge_specs(Spec1, Spec2, Spec).
507
508same_file_location(L1, L2) :-
509 #{file:File} :< L1,
510 #{file:File} :< L2.
511
512same_location(L, L, L).
513same_location(#{file:F1}, #{file:F2}, #{file:F}) :-
514 best_same_file(F1, F2, F).
515same_location(#{file:F1, line:Line}, #{file:F2}, #{file:F, line:Line}) :-
516 best_same_file(F1, F2, F).
517same_location(#{file:F1}, #{file:F2, line:Line}, #{file:F, line:Line}) :-
518 best_same_file(F1, F2, F).
519
520best_same_file(F1, F2, F) :-
521 catch(same_file(F1, F2), _, fail),
522 !,
523 atom_length(F1, L1),
524 atom_length(F2, L2),
525 ( L1 < L2
526 -> F = F1
527 ; F = F2
528 ).
529
530merge_specs(Spec, Spec, Spec) :-
531 !.
532merge_specs(file(F1), file(F2), file(F)) :-
533 best_same_file(F1, F2, F),
534 !.
535merge_specs(Spec1, Spec2, Spec) :-
536 merge_specs_(Spec1, Spec2, Spec),
537 !.
538merge_specs(Spec1, Spec2, Spec) :-
539 merge_specs_(Spec2, Spec1, Spec),
540 !.
541
542merge_specs_(FileSpec, Spec, Spec) :-
543 is_filespec(FileSpec).
544
545is_filespec(file(_)) => true.
546is_filespec(source_file(_)) => true.
547is_filespec(Term),
548 compound(Term),
549 compound_name_arguments(Term, Alias, [_Arg]),
550 user:file_search_path(Alias, _) => true.
551is_filespec(_) =>
552 fail.
553
554better_spec(class(_), module(_)).
555better_spec(_, FileSpec) :-
556 is_filespec(FileSpec).
557
562
563do_select_location(Pairs, Spec, Location) :-
564 select_location(Pairs, Spec, Location), 565 !,
566 Location \== [].
567do_select_location([], Spec, _) :-
568 !,
569 print_message(warning, edit(not_found(Spec))),
570 fail.
571do_select_location([#{file:File}-file(File)], _, Location) :-
572 !,
573 Location = #{file:File}.
574do_select_location([Location-_Spec], _, Location) :-
575 existing_location(Location),
576 !.
577do_select_location(Pairs, _, Location) :-
578 foldl(number_location, Pairs, NPairs, 1, End),
579 print_message(help, edit(select(NPairs))),
580 ( End == 1
581 -> fail
582 ; Max is End - 1,
583 user_selection(Max, I),
584 memberchk(I-(Location-_Spec), NPairs)
585 ).
586
592
593existing_location(Location) :-
594 exists_location(Location),
595 !.
596existing_location(Location) :-
597 #{file:File} :< Location,
598 access_file(File, read).
599
600number_location(Pair, N-Pair, N, N1) :-
601 Pair = Location-_Spec,
602 existing_location(Location),
603 !,
604 N1 is N+1.
605number_location(Pair, 0-Pair, N, N).
606
607user_selection(Max, I) :-
608 user_select(Max, I),
609 !.
610user_selection(Max, I) :-
611 print_message(help, edit(choose(Max))),
612 read_number(Max, I).
613
617
618read_number(Max, X) :-
619 Max < 10,
620 !,
621 get_single_char(C),
622 put_code(user_error, C),
623 between(0'0, 0'9, C),
624 X is C - 0'0.
625read_number(_, X) :-
626 read_line_to_string(user_input, String),
627 number_string(X, String).
628
629
630 633
634:- multifile
635 prolog:message/3. 636
637prolog:message(edit(Msg)) -->
638 message(Msg).
639
640message(not_found(Spec)) -->
641 [ 'Cannot find anything to edit from "~p"'-[Spec] ],
642 ( { atom(Spec) }
643 -> [ nl, ' Use edit(file(~q)) to create a new file'-[Spec] ]
644 ; []
645 ).
646message(select(NPairs)) -->
647 { \+ (member(N-_, NPairs), N > 0) },
648 !,
649 [ 'Found the following locations:', nl ],
650 sequence(target, [nl], NPairs).
651message(select(NPairs)) -->
652 [ 'Please select item to edit:', nl ],
653 sequence(target, [nl], NPairs).
654message(choose(_Max)) -->
655 [ nl, 'Your choice? ', flush ].
656message(waiting_for_editor) -->
657 [ 'Waiting for editor ... ', flush ].
658message(make) -->
659 [ 'Running make to reload modified files' ].
660message(canceled) -->
661 [ 'Editor returned failure; skipped make/0 to reload files' ].
662
663target(0-(Location-Spec)) ==>
664 [ ansi(warning, '~t*~3| ', [])],
665 edit_specifier(Spec),
666 [ '~t~32|' ],
667 edit_location(Location, false),
668 [ ansi(warning, ' (no source available)', [])].
669target(N-(Location-Spec)) ==>
670 [ ansi(bold, '~t~d~3| ', [N])],
671 edit_specifier(Spec),
672 [ '~t~32|' ],
673 edit_location(Location, true).
674
675edit_specifier(Module:Name/Arity) ==>
676 [ '~w:'-[Module],
677 ansi(code, '~w/~w', [Name, Arity]) ].
678edit_specifier(file(_Path)) ==>
679 [ '<file>' ].
680edit_specifier(source_file(_Path)) ==>
681 [ '<loaded file>' ].
682edit_specifier(include_file(_Path)) ==>
683 [ '<included file>' ].
684edit_specifier(Term) ==>
685 [ '~p'-[Term] ].
686
687edit_location(Location, false) ==>
688 { location_label(Location, Label) },
689 [ ansi(warning, '~s', [Label]) ].
690edit_location(Location, true) ==>
691 { location_label(Location, Label),
692 location_url(Location, URL)
693 },
694 [ url(URL, Label) ].
695
696location_label(Location, Label) :-
697 #{file:File, line:Line} :< Location,
698 !,
699 short_filename(File, ShortFile),
700 format(string(Label), '~w:~d', [ShortFile, Line]).
701location_label(Location, Label) :-
702 #{file:File} :< Location,
703 !,
704 short_filename(File, ShortFile),
705 format(string(Label), '~w', [ShortFile]).
706
707location_url(Location, File:Line:LinePos) :-
708 #{file:File, line:Line, linepos:LinePos} :< Location,
709 !.
710location_url(Location, File:Line) :-
711 #{file:File, line:Line} :< Location,
712 !.
713location_url(Location, File) :-
714 #{file:File} :< Location.
715
721
722short_filename(Path, Spec) :-
723 working_directory(Here, Here),
724 atom_concat(Here, Local0, Path),
725 !,
726 remove_leading_slash(Local0, Spec).
727short_filename(Path, Spec) :-
728 findall(LenAlias, aliased_path(Path, LenAlias), Keyed),
729 keysort(Keyed, [_-Spec|_]).
730short_filename(Path, Path).
731
732aliased_path(Path, Len-Spec) :-
733 setof(Alias, file_alias_path(Alias), Aliases),
734 member(Alias, Aliases),
735 Alias \== autoload, 736 Term =.. [Alias, '.'],
737 absolute_file_name(Term, Prefix,
738 [ file_type(directory),
739 file_errors(fail),
740 solutions(all)
741 ]),
742 atom_concat(Prefix, Local0, Path),
743 remove_leading_slash(Local0, Local1),
744 remove_extension(Local1, Local2),
745 unquote_segments(Local2, Local),
746 atom_length(Local2, Len),
747 Spec =.. [Alias, Local].
748
749file_alias_path(Alias) :-
750 user:file_search_path(Alias, _).
751
752remove_leading_slash(Path, Local) :-
753 atom_concat(/, Local, Path),
754 !.
755remove_leading_slash(Path, Path).
756
757remove_extension(File0, File) :-
758 file_name_extension(File, Ext, File0),
759 user:prolog_file_type(Ext, source),
760 !.
761remove_extension(File, File).
762
763unquote_segments(File, Segments) :-
764 split_string(File, "/", "/", SegmentStrings),
765 maplist(atom_string, SegmentList, SegmentStrings),
766 maplist(no_quote_needed, SegmentList),
767 !,
768 segments(SegmentList, Segments).
769unquote_segments(File, File).
770
771
772no_quote_needed(A) :-
773 format(atom(Q), '~q', [A]),
774 Q == A.
775
776segments([Segment], Segment) :-
777 !.
778segments(List, A/Segment) :-
779 append(L1, [Segment], List),
780 !,
781 segments(L1, A).
782
783
784 787
788load_extensions :-
789 load,
790 fail.
791load_extensions.
792
793:- load_extensions.