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