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:- if(exists_source(library(editline))). 49:- autoload(library(editline), [el_history_events/2]). 50:- endif. 51
53
61
62:- multifile
63 locate/3, 64 locate/2, 65 select_location/3, 66 exists_location/1, 67 user_select/2, 68 edit_source/1, 69 edit_command/2, 70 load/0. 71
72:- public
73 locations/2, 74 predicate_location/2, 75 addr2location/3. 76
85
86edit(Spec) :-
87 notrace(edit_no_trace(Spec)).
88
89edit_no_trace(Spec) :-
90 var(Spec),
91 !,
92 throw(error(instantiation_error, _)).
93edit_no_trace(Spec) :-
94 locations(Spec, Pairs),
95 do_select_location(Pairs, Spec, Location),
96 do_edit_source(Location).
97
109
110edit :-
111 current_prolog_flag(associated_file, File),
112 !,
113 edit(file(File)).
114edit :-
115 '$cmd_option_val'(script_file, OsFiles),
116 OsFiles = [OsFile],
117 !,
118 prolog_to_os_filename(File, OsFile),
119 edit(file(File)).
120:- if(exists_source(library(editline))). 121edit :-
122 session_loaded_file(File),
123 !,
124 edit(file(File)).
125:- endif. 126edit :-
127 throw(error(context_error(edit, no_default_file), _)).
128
129:- if(exists_source(library(editline))). 130session_loaded_file(File) :-
131 catch(el_history_events(user_input, Events),
132 error(_,_), fail),
133 member(_-Event, Events),
134 catch(term_string(Command, Event), error(_,_), fail),
135 load_command(Command, File0),
136 ( is_list(File0)
137 -> member(File1, File0)
138 ; File1 = File0
139 ),
140 catch(absolute_file_name(File1, File,
141 [ file_type(source),
142 file_errors(fail)
143 ]),
144 error(_,_), fail),
145 source_file(File),
146 !.
147
148load_command([File|T], [File|T]).
149load_command(consult(File), File).
150load_command(use_module(File), File).
151:- endif. 152
160
161locations(Spec, Locations) :-
162 load_extensions,
163 findall(Location-FullSpec,
164 locate(Spec, FullSpec, Location),
165 Pairs0),
166 sort(Pairs0, Pairs1),
167 merge_locations(Pairs1, Locations).
168
169
170 173
175
176locate(FileSpec:Line, file(Path, line(Line)), #{file:Path, line:Line}) :-
177 integer(Line), Line >= 1,
178 ground(FileSpec), 179 !,
180 locate(FileSpec, _, #{file:Path}).
181locate(FileSpec:Line:LinePos,
182 file(Path, line(Line), linepos(LinePos)),
183 #{file:Path, line:Line, linepos:LinePos}) :-
184 integer(Line), Line >= 1,
185 integer(LinePos), LinePos >= 1,
186 ground(FileSpec), 187 !,
188 locate(FileSpec, _, #{file:Path}).
189locate(Path, file(Path), #{file:Path}) :-
190 atom(Path),
191 exists_file(Path).
192locate(Pattern, file(Path), #{file:Path}) :-
193 atom(Pattern),
194 catch(expand_file_name(Pattern, Files), error(_,_), fail),
195 member(Path, Files),
196 exists_file(Path).
197locate(FileBase, file(File), #{file:File}) :-
198 atom(FileBase),
199 find_source(FileBase, File).
200locate(FileSpec, file(File), #{file:File}) :-
201 is_file_search_spec(FileSpec),
202 find_source(FileSpec, File).
203locate(FileBase, source_file(Path), #{file:Path}) :-
204 atom(FileBase),
205 source_file(Path),
206 file_base_name(Path, File),
207 ( File == FileBase
208 -> true
209 ; file_name_extension(FileBase, _, File)
210 ).
211locate(FileBase, include_file(Path), #{file:Path}) :-
212 atom(FileBase),
213 setof(Path, include_file(Path), Paths),
214 member(Path, Paths),
215 file_base_name(Path, File),
216 ( File == FileBase
217 -> true
218 ; file_name_extension(FileBase, _, File)
219 ).
220locate(Name, FullSpec, Location) :-
221 atom(Name),
222 locate(Name/_, FullSpec, Location).
223locate(Name/Arity, Module:Name/Arity, Location) :-
224 locate(Module:Name/Arity, Location).
225locate(Name//DCGArity, FullSpec, Location) :-
226 ( integer(DCGArity)
227 -> Arity is DCGArity+2,
228 locate(Name/Arity, FullSpec, Location)
229 ; locate(Name/_, FullSpec, Location) 230 ).
231locate(Name/Arity, library(File), #{file:PlPath}) :-
232 atom(Name),
233 '$in_library'(Name, Arity, Path),
234 ( absolute_file_name(library(.), Dir,
235 [ file_type(directory),
236 solutions(all)
237 ]),
238 atom_concat(Dir, File0, Path),
239 atom_concat(/, File, File0)
240 -> find_source(Path, PlPath)
241 ; fail
242 ).
243locate(Module:Name, Module:Name/Arity, Location) :-
244 locate(Module:Name/Arity, Location).
245locate(Module:Head, Module:Name/Arity, Location) :-
246 callable(Head),
247 \+ ( Head = (PName/_),
248 atom(PName)
249 ),
250 functor(Head, Name, Arity),
251 locate(Module:Name/Arity, Location).
252locate(Spec, module(Spec), Location) :-
253 locate(module(Spec), Location).
254locate(Spec, Spec, Location) :-
255 locate(Spec, Location).
256
257include_file(Path) :-
258 source_file_property(Path, included_in(_,_)).
259
263
264is_file_search_spec(Spec) :-
265 compound(Spec),
266 compound_name_arguments(Spec, Alias, [Arg]),
267 is_file_spec(Arg),
268 user:file_search_path(Alias, _),
269 !.
270
271is_file_spec(Name), atom(Name) => true.
272is_file_spec(Name), string(Name) => true.
273is_file_spec(Term), cyclic_term(Term) => fail.
274is_file_spec(A/B) => is_file_spec(A), is_file_spec(B).
275is_file_spec(_) => fail.
276
281
282find_source(FileSpec, File) :-
283 catch(absolute_file_name(FileSpec, File0,
284 [ file_type(prolog),
285 access(read),
286 file_errors(fail)
287 ]),
288 error(_,_), fail),
289 prolog_source(File0, File).
290
291prolog_source(File0, File) :-
292 file_name_extension(_, Ext, File0),
293 user:prolog_file_type(Ext, qlf),
294 !,
295 '$qlf_module'(File0, Info),
296 File = Info.get(file).
297prolog_source(File, File).
298
299
303
304locate(file(File, line(Line)), #{file:File, line:Line}).
305locate(file(File), #{file:File}).
306locate(Module:Name/Arity, Location) :-
307 ( atom(Name), integer(Arity)
308 -> functor(Head, Name, Arity)
309 ; Head = _ 310 ),
311 ( ( var(Module)
312 ; var(Name)
313 )
314 -> NonImport = true
315 ; NonImport = false
316 ),
317 current_predicate(Name, Module:Head),
318 \+ ( NonImport == true,
319 Module \== system,
320 predicate_property(Module:Head, imported_from(_))
321 ),
322 functor(Head, Name, Arity), 323 predicate_location(Module:Head, Location).
324locate(module(Module), Location) :-
325 atom(Module),
326 module_property(Module, file(Path)),
327 ( module_property(Module, line_count(Line))
328 -> Location = #{file:Path, line:Line}
329 ; Location = #{file:Path}
330 ).
331locate(breakpoint(Id), Location) :-
332 integer(Id),
333 breakpoint_property(Id, clause(Ref)),
334 ( breakpoint_property(Id, file(File)),
335 breakpoint_property(Id, line_count(Line))
336 -> Location = #{file:File, line:Line}
337 ; locate(clause(Ref), Location)
338 ).
339locate(clause(Ref), #{file:File, line:Line}) :-
340 clause_property(Ref, file(File)),
341 clause_property(Ref, line_count(Line)).
342locate(clause(Ref, _PC), #{file:File, line:Line}) :- 343 clause_property(Ref, file(File)),
344 clause_property(Ref, line_count(Line)).
345
352
353predicate_location(Pred, #{file:File, line:Line}) :-
354 copy_term(Pred, Pred2),
355 distinct(Primary, primary_predicate(Pred2, Primary)),
356 ignore(Pred = Primary),
357 '$predicate_source_location'(Primary, File:Line).
358
359primary_predicate(Pred, Primary) :-
360 ( predicate_property(Pred, imported_from(Source))
361 -> strip_module(Pred, _, Head),
362 Primary = Source:Head
363 ; Primary = Pred
364 ).
365
366
370
371addr2location(Address, File, Line) :-
372 '$addr2line'(Address, Source),
373 '$addr2line_location'(Source, File, Line).
374
375
376 379
391
392do_edit_source(Location) :- 393 edit_source(Location),
394 !.
395do_edit_source(Location) :- 396 current_prolog_flag(editor, Editor),
397 is_pceemacs(Editor),
398 current_prolog_flag(gui, true),
399 !,
400 location_url(Location, URL), 401 run_pce_emacs(URL).
402do_edit_source(Location) :- 403 external_edit_command(Location, Command),
404 print_message(informational, edit(waiting_for_editor)),
405 ( catch(shell(Command), E,
406 (print_message(warning, E),
407 fail))
408 -> print_message(informational, edit(make)),
409 make
410 ; print_message(informational, edit(canceled))
411 ).
412
413external_edit_command(Location, Command) :-
414 #{file:File, line:Line} :< Location,
415 editor(Editor),
416 file_base_name(Editor, EditorFile),
417 file_name_extension(Base, _, EditorFile),
418 edit_command(Base, Cmd),
419 prolog_to_os_filename(File, OsFile),
420 atom_codes(Cmd, S0),
421 substitute('%e', Editor, S0, S1),
422 substitute('%f', OsFile, S1, S2),
423 substitute('%d', Line, S2, S),
424 !,
425 atom_codes(Command, S).
426external_edit_command(Location, Command) :-
427 #{file:File} :< Location,
428 editor(Editor),
429 file_base_name(Editor, EditorFile),
430 file_name_extension(Base, _, EditorFile),
431 edit_command(Base, Cmd),
432 prolog_to_os_filename(File, OsFile),
433 atom_codes(Cmd, S0),
434 substitute('%e', Editor, S0, S1),
435 substitute('%f', OsFile, S1, S),
436 \+ substitute('%d', 1, S, _),
437 !,
438 atom_codes(Command, S).
439external_edit_command(Location, Command) :-
440 #{file:File} :< Location,
441 editor(Editor),
442 format(string(Command), '"~w" "~w"', [Editor, File]).
443
444is_pceemacs(pce_emacs).
445is_pceemacs(built_in).
446
450
451run_pce_emacs(URL) :-
452 autoload_call(in_pce_thread(autoload_call(emacs(URL)))).
453
457
458editor(Editor) :- 459 current_prolog_flag(editor, Editor),
460 ( sub_atom(Editor, 0, _, _, $)
461 -> sub_atom(Editor, 1, _, 0, Var),
462 catch(getenv(Var, Editor), _, fail), !
463 ; Editor == default
464 -> catch(getenv('EDITOR', Editor), _, fail), !
465 ; \+ is_pceemacs(Editor)
466 -> !
467 ).
468editor(Editor) :- 469 getenv('EDITOR', Editor),
470 !.
471editor(vi) :- 472 current_prolog_flag(unix, true),
473 !.
474editor(notepad) :-
475 current_prolog_flag(windows, true),
476 !.
477editor(_) :- 478 throw(error(existence_error(editor), _)).
479
488
489
490edit_command(vi, '%e +%d \'%f\'').
491edit_command(vi, '%e \'%f\'').
492edit_command(emacs, '%e +%d \'%f\'').
493edit_command(emacs, '%e \'%f\'').
494edit_command(notepad, '"%e" "%f"').
495edit_command(wordpad, '"%e" "%f"').
496edit_command(uedit32, '%e "%f/%d/0"'). 497edit_command(jedit, '%e -wait \'%f\' +line:%d').
498edit_command(jedit, '%e -wait \'%f\'').
499edit_command(edit, '%e %f:%d'). 500edit_command(edit, '%e %f').
501
502edit_command(emacsclient, Command) :- edit_command(emacs, Command).
503edit_command(vim, Command) :- edit_command(vi, Command).
504edit_command(nvim, Command) :- edit_command(vi, Command).
505
506substitute(FromAtom, ToAtom, Old, New) :-
507 atom_codes(FromAtom, From),
508 ( atom(ToAtom)
509 -> atom_codes(ToAtom, To)
510 ; number_codes(ToAtom, To)
511 ),
512 append(Pre, S0, Old),
513 append(From, Post, S0) ->
514 append(Pre, To, S1),
515 append(S1, Post, New),
516 !.
517substitute(_, _, Old, Old).
518
519
520 523
524merge_locations(Locations0, Locations) :-
525 append(Before, [L1|Rest], Locations0),
526 select(L2, Rest, Rest1),
527 merge_location(L1, L2, Loc),
528 !,
529 append([Before, [Loc], Rest1], Locations1),
530 merge_locations(Locations1, Locations).
531merge_locations(Locations, Locations).
532
533merge_location(Loc1-Spec1, Loc2-Spec2, Loc1-Spec1) :-
534 same_file_location(Loc1,Loc2),
535 better_spec(Spec1, Spec2).
536merge_location(Loc1-Spec1, Loc2-Spec2, Loc-Spec) :-
537 same_location(Loc1, Loc2, Loc),
538 merge_specs(Spec1, Spec2, Spec).
539
540same_file_location(L1, L2) :-
541 #{file:File} :< L1,
542 #{file:File} :< L2.
543
544same_location(L, L, L).
545same_location(#{file:F1}, #{file:F2}, #{file:F}) :-
546 best_same_file(F1, F2, F).
547same_location(#{file:F1, line:Line}, #{file:F2}, #{file:F, line:Line}) :-
548 best_same_file(F1, F2, F).
549same_location(#{file:F1}, #{file:F2, line:Line}, #{file:F, line:Line}) :-
550 best_same_file(F1, F2, F).
551
552best_same_file(F1, F2, F) :-
553 catch(same_file(F1, F2), _, fail),
554 !,
555 atom_length(F1, L1),
556 atom_length(F2, L2),
557 ( L1 < L2
558 -> F = F1
559 ; F = F2
560 ).
561
562merge_specs(Spec, Spec, Spec) :-
563 !.
564merge_specs(file(F1), file(F2), file(F)) :-
565 best_same_file(F1, F2, F),
566 !.
567merge_specs(Spec1, Spec2, Spec) :-
568 merge_specs_(Spec1, Spec2, Spec),
569 !.
570merge_specs(Spec1, Spec2, Spec) :-
571 merge_specs_(Spec2, Spec1, Spec),
572 !.
573
574merge_specs_(FileSpec, Spec, Spec) :-
575 is_filespec(FileSpec).
576
577is_filespec(file(_)) => true.
578is_filespec(source_file(_)) => true.
579is_filespec(Term),
580 compound(Term),
581 compound_name_arguments(Term, Alias, [_Arg]),
582 user:file_search_path(Alias, _) => true.
583is_filespec(_) =>
584 fail.
585
586better_spec(class(_), module(_)).
587better_spec(_, FileSpec) :-
588 is_filespec(FileSpec).
589
594
595do_select_location(Pairs, Spec, Location) :-
596 select_location(Pairs, Spec, Location), 597 !,
598 Location \== [].
599do_select_location([], Spec, _) :-
600 !,
601 print_message(warning, edit(not_found(Spec))),
602 fail.
603do_select_location([#{file:File}-file(File)], _, Location) :-
604 !,
605 Location = #{file:File}.
606do_select_location([Location-_Spec], _, Location) :-
607 existing_location(Location),
608 !.
609do_select_location(Pairs, _, Location) :-
610 foldl(number_location, Pairs, NPairs, 1, End),
611 print_message(help, edit(select(NPairs))),
612 ( End == 1
613 -> fail
614 ; Max is End - 1,
615 user_selection(Max, I),
616 memberchk(I-(Location-_Spec), NPairs)
617 ).
618
624
625existing_location(Location) :-
626 exists_location(Location),
627 !.
628existing_location(Location) :-
629 #{file:File} :< Location,
630 access_file(File, read).
631
632number_location(Pair, N-Pair, N, N1) :-
633 Pair = Location-_Spec,
634 existing_location(Location),
635 !,
636 N1 is N+1.
637number_location(Pair, 0-Pair, N, N).
638
639user_selection(Max, I) :-
640 user_select(Max, I),
641 !.
642user_selection(Max, I) :-
643 print_message(help, edit(choose(Max))),
644 read_number(Max, I).
645
649
650read_number(Max, X) :-
651 Max < 10,
652 !,
653 get_single_char(C),
654 put_code(user_error, C),
655 between(0'0, 0'9, C),
656 X is C - 0'0.
657read_number(_, X) :-
658 read_line_to_string(user_input, String),
659 number_string(X, String).
660
661
662 665
666:- multifile
667 prolog:message/3. 668
669prolog:message(edit(Msg)) -->
670 message(Msg).
671
672message(not_found(Spec)) -->
673 [ 'Cannot find anything to edit from "~p"'-[Spec] ],
674 ( { atom(Spec) }
675 -> [ nl, ' Use edit(file(~q)) to create a new file'-[Spec] ]
676 ; []
677 ).
678message(select(NPairs)) -->
679 { \+ (member(N-_, NPairs), N > 0) },
680 !,
681 [ 'Found the following locations:', nl ],
682 sequence(target, [nl], NPairs).
683message(select(NPairs)) -->
684 [ 'Please select item to edit:', nl ],
685 sequence(target, [nl], NPairs).
686message(target(Location-Spec, N)) -->
687 ['~t~d~3| '-[N]],
688 edit_specifier(Spec),
689 [ '\t' ],
690 edit_location(Location, true).
691message(choose(_Max)) -->
692 [ nl, 'Your choice? ', flush ].
693message(waiting_for_editor) -->
694 [ 'Waiting for editor ... ', flush ].
695message(make) -->
696 [ 'Running make to reload modified files' ].
697message(canceled) -->
698 [ 'Editor returned failure; skipped make/0 to reload files' ].
699
700target(0-(Location-Spec)) ==>
701 [ ansi(warning, '~t*~3| ', [])],
702 edit_specifier(Spec),
703 [ '~t~32|' ],
704 edit_location(Location, false),
705 [ ansi(warning, ' (no source available)', [])].
706target(N-(Location-Spec)) ==>
707 [ ansi(bold, '~t~d~3| ', [N])],
708 edit_specifier(Spec),
709 [ '~t~32|' ],
710 edit_location(Location, true).
711
712edit_specifier(Module:Name/Arity) ==>
713 [ '~w:'-[Module],
714 ansi(code, '~w/~w', [Name, Arity]) ].
715edit_specifier(file(_Path)) ==>
716 [ '<file>' ].
717edit_specifier(source_file(_Path)) ==>
718 [ '<loaded file>' ].
719edit_specifier(include_file(_Path)) ==>
720 [ '<included file>' ].
721edit_specifier(Term) ==>
722 [ '~p'-[Term] ].
723
724edit_location(Location, false) ==>
725 { location_label(Location, Label) },
726 [ ansi(warning, '~s', [Label]) ].
727edit_location(Location, true) ==>
728 { location_label(Location, Label),
729 location_url(Location, URL)
730 },
731 [ url(URL, Label) ].
732
733location_label(Location, Label) :-
734 #{file:File, line:Line} :< Location,
735 !,
736 short_filename(File, ShortFile),
737 format(string(Label), '~w:~d', [ShortFile, Line]).
738location_label(Location, Label) :-
739 #{file:File} :< Location,
740 !,
741 short_filename(File, ShortFile),
742 format(string(Label), '~w', [ShortFile]).
743
744location_url(Location, File:Line:LinePos) :-
745 #{file:File, line:Line, linepos:LinePos} :< Location,
746 !.
747location_url(Location, File:Line) :-
748 #{file:File, line:Line} :< Location,
749 !.
750location_url(Location, File) :-
751 #{file:File} :< Location.
752
758
759short_filename(Path, Spec) :-
760 working_directory(Here, Here),
761 atom_concat(Here, Local0, Path),
762 !,
763 remove_leading_slash(Local0, Spec).
764short_filename(Path, Spec) :-
765 findall(LenAlias, aliased_path(Path, LenAlias), Keyed),
766 keysort(Keyed, [_-Spec|_]).
767short_filename(Path, Path).
768
769aliased_path(Path, Len-Spec) :-
770 setof(Alias, file_alias_path(Alias), Aliases),
771 member(Alias, Aliases),
772 Alias \== autoload, 773 Term =.. [Alias, '.'],
774 absolute_file_name(Term, Prefix,
775 [ file_type(directory),
776 file_errors(fail),
777 solutions(all)
778 ]),
779 atom_concat(Prefix, Local0, Path),
780 remove_leading_slash(Local0, Local1),
781 remove_extension(Local1, Local2),
782 unquote_segments(Local2, Local),
783 atom_length(Local2, Len),
784 Spec =.. [Alias, Local].
785
786file_alias_path(Alias) :-
787 user:file_search_path(Alias, _).
788
789remove_leading_slash(Path, Local) :-
790 atom_concat(/, Local, Path),
791 !.
792remove_leading_slash(Path, Path).
793
794remove_extension(File0, File) :-
795 file_name_extension(File, Ext, File0),
796 user:prolog_file_type(Ext, source),
797 !.
798remove_extension(File, File).
799
800unquote_segments(File, Segments) :-
801 split_string(File, "/", "/", SegmentStrings),
802 maplist(atom_string, SegmentList, SegmentStrings),
803 maplist(no_quote_needed, SegmentList),
804 !,
805 segments(SegmentList, Segments).
806unquote_segments(File, File).
807
808
809no_quote_needed(A) :-
810 format(atom(Q), '~q', [A]),
811 Q == A.
812
813segments([Segment], Segment) :-
814 !.
815segments(List, A/Segment) :-
816 append(L1, [Segment], List),
817 !,
818 segments(L1, A).
819
820
821 824
825load_extensions :-
826 load,
827 fail.
828load_extensions.
829
830:- load_extensions.