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(solution_sequences), [distinct/2]). 48
49
51
59
60:- multifile
61 locate/3, 62 locate/2, 63 select_location/3, 64 exists_location/1, 65 user_select/2, 66 edit_source/1, 67 edit_command/2, 68 load/0. 69
70:- public
71 locations/2, 72 predicate_location/2, 73 addr2location/3. 74
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
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
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 138
140
141locate(FileSpec:Line, file(Path, line(Line)), #{file:Path, line:Line}) :-
142 integer(Line), Line >= 1,
143 ground(FileSpec), 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), 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) 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
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
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
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 = _ 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), 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}) :- 308 clause_property(Ref, file(File)),
309 clause_property(Ref, line_count(Line)).
310
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
335
336addr2location(Address, File, Line) :-
337 '$addr2line'(Address, Source),
338 '$addr2line_location'(Source, File, Line).
339
340
341 344
356
357do_edit_source(Location) :- 358 edit_source(Location),
359 !.
360do_edit_source(Location) :- 361 current_prolog_flag(editor, Editor),
362 is_pceemacs(Editor),
363 current_prolog_flag(gui, true),
364 !,
365 location_url(Location, URL), 366 run_pce_emacs(URL).
367do_edit_source(Location) :- 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
415
416run_pce_emacs(URL) :-
417 autoload_call(in_pce_thread(autoload_call(emacs(URL)))).
418
422
423editor(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) :- 434 getenv('EDITOR', Editor),
435 !.
436editor(vi) :- 437 current_prolog_flag(unix, true),
438 !.
439editor(notepad) :-
440 current_prolog_flag(windows, true),
441 !.
442editor(_) :- 443 throw(error(existence_error(editor), _)).
444
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"'). 462edit_command(jedit, '%e -wait \'%f\' +line:%d').
463edit_command(jedit, '%e -wait \'%f\'').
464edit_command(edit, '%e %f:%d'). 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 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
559
560do_select_location(Pairs, Spec, Location) :-
561 select_location(Pairs, Spec, Location), 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
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
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 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
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, 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 784
785load_extensions :-
786 load,
787 fail.
788load_extensions.
789
790:- load_extensions.