1:- module(lsp_utils, [called_at/3,
2 defined_at/3,
3 name_callable/2,
4 relative_ref_location/4,
5 help_at_position/4,
6 clause_in_file_at_position/3,
7 clause_variable_positions/3,
8 usemod_filespec_position/4,
9 seek_to_line/2,
10 linechar_offset/3,
11 url_path/2,
12 unlimited//1
13 ]).
22:- use_module(library(apply_macros)). 23:- use_module(library(apply), [maplist/3, exclude/3]). 24:- use_module(library(dcg/basics), [blanks//0]). 25:- use_module(library(prolog_xref)). 26:- use_module(library(prolog_source), [read_source_term_at_location/3]). 27:- use_module(library(help)). 28:- use_module(library(lynx/html_text), [html_text/1]). 29:- use_module(library(solution_sequences), [distinct/2]). 30:- use_module(library(lists), [append/3, member/2, selectchk/4]). 31:- use_module(library(sgml), [load_html/3]). 32:- use_module(library(yall)). 33
34:- include('_lsp_path_add.pl'). 35
36:- use_module(lsp(lsp_reading_source), [ file_lines_start_end/2,
37 read_term_positions/2,
38 read_term_positions/4,
39 find_in_term_with_positions/5,
40 position_to_match/3,
41 file_offset_line_position/4 ]). 42
43:- if(current_predicate(xref_called/5)).
48called_at(Path, Clause, Locations) :-
49 setof(L, Path^Clause^Locs^(
50 called_at_(Path, Clause, Locs),
51 member(L, Locs)
52 ),
53 Locations), !.
54called_at(Path, Clause, Locations) :-
55 name_callable(Clause, Callable),
56 xref_source(Path),
57 xref_called(Path, Callable, _By, _, CallerLine),
58 59 succ(CallerLine0, CallerLine),
60 Locations = [_{range: _{start: _{line: CallerLine0, character: 0},
61 end: _{line: CallerLine, character: 0}}}].
62
63called_at_(Path, Clause, Locations) :-
64 name_callable(Clause, Callable),
65 xref_source(Path),
66 xref_called(Path, Callable, _By, _, CallerLine),
67 file_lines_start_end(Path, LineCharRange),
68 file_offset_line_position(LineCharRange, Offset, CallerLine, 0),
69 read_term_positions(Path, Offset, Offset, TermInfos),
70 Clause = FuncName/Arity,
71 find_occurences_of_callable(Path, FuncName, Arity, TermInfos, Matches, []),
72 maplist(position_to_match(LineCharRange), Matches, Locations).
73called_at_(Path, Clause, Locations) :-
74 xref_source(Path),
75 Clause = FuncName/Arity,
76 DcgArity is Arity + 2,
77 DcgClause = FuncName/DcgArity,
78 name_callable(DcgClause, DcgCallable),
79 xref_defined(Path, DcgCallable, dcg),
80 name_callable(DcgClause, DcgCallable),
81 xref_called(Path, DcgCallable, _By, _, CallerLine),
82 file_lines_start_end(Path, LineCharRange),
83 file_offset_line_position(LineCharRange, Offset, CallerLine, 0),
84 read_term_positions(Path, Offset, Offset, TermInfos),
85 find_occurences_of_callable(Path, FuncName, DcgArity, TermInfos, Matches, Tail0),
86 87 88 89 find_occurences_of_callable(Path, FuncName, Arity, TermInfos, Tail0, []),
90 maplist(position_to_match(LineCharRange), Matches, Locations).
91:- else. 92called_at(Path, Callable, By, Ref) :-
93 xref_called(Path, Callable, By),
94 xref_defined(Path, By, Ref).
95:- endif. 96
97find_occurences_of_callable(_, _, _, [], Tail, Tail).
98find_occurences_of_callable(Path, FuncName, Arity, [TermInfo|TermInfos], Matches, Tail) :-
99 FindState = in_meta(false),
100 find_in_term_with_positions(term_matches_callable(FindState, Path, FuncName, Arity),
101 TermInfo.term, TermInfo.subterm, Matches, Tail0),
102 find_occurences_of_callable(Path, FuncName, Arity, TermInfos, Tail0, Tail).
103
104term_matches_callable(FindState, Path, FuncName, Arity, Term, Position) :-
105 arg(1, Position, Start),
106 arg(2, Position, End),
107 ( arg(1, FindState, in_meta(_, MStart, MEnd)),
108 once( Start > MEnd ; End < MStart )
109 -> nb_setarg(1, FindState, false)
110 ; true ),
111 term_matches_callable_(FindState, Path, FuncName, Arity, Term, Position).
112
113term_matches_callable_(_, _, FuncName, Arity, Term, _) :-
114 nonvar(Term), Term = FuncName/Arity.
115term_matches_callable_(_, _, FuncName, Arity, Term, _) :-
116 nonvar(Term),
117 functor(T, FuncName, Arity),
118 Term = T, !.
119term_matches_callable_(State, _, FuncName, Arity, Term, _) :-
120 nonvar(Term),
121 122 arg(1, State, in_meta(N, _, _)),
123 MArity is Arity - N,
124 functor(T, FuncName, MArity),
125 Term = T, !.
126term_matches_callable_(State, Path, _, _, Term, Position) :-
127 nonvar(Term), compound(Term),
128 compound_name_arity(Term, ThisName, ThisArity),
129 name_callable(ThisName/ThisArity, Callable),
130 xref_meta(Path, Callable, Called),
131 member(E, Called), nonvar(E), E = _+N, integer(N),
132 arg(1, Position, Start),
133 arg(2, Position, End),
134 nb_setarg(1, State, in_meta(N, Start, End)),
135 fail.
140url_path(Url, Path) :-
141 current_prolog_flag(windows, true),
142 143 144 145 atom_concat('file:///', Path, Url), !.
146url_path(Url, Path) :-
147 atom_concat('file://', Path, Url).
148
149defined_at(Path, Name/Arity, Location) :-
150 name_callable(Name/Arity, Callable),
151 xref_source(Path),
152 xref_defined(Path, Callable, Ref),
153 url_path(Doc, Path),
154 relative_ref_location(Doc, Callable, Ref, Location).
155defined_at(Path, Name/Arity, Location) :-
156 157 DcgArity is Arity + 2,
158 name_callable(Name/DcgArity, Callable),
159 xref_source(Path),
160 xref_defined(Path, Callable, Ref),
161 url_path(Doc, Path),
162 relative_ref_location(Doc, Callable, Ref, Location).
163
164collapse_adjacent([X|Rst], [X|CRst]) :-
165 collapse_adjacent(X, Rst, CRst).
166collapse_adjacent(X, [Y|Rst], CRst) :-
167 succ(X, Y), !,
168 collapse_adjacent(Y, Rst, CRst).
169collapse_adjacent(_, [X|Rst], [X|CRst]) :- !,
170 collapse_adjacent(X, Rst, CRst).
171collapse_adjacent(_, [], []).
177name_callable(Name/0, Name) :- atom(Name), !.
178name_callable(Name/Arity, Callable) :-
179 length(FakeArgs, Arity),
180 Callable =.. [Name|FakeArgs], !.
186relative_ref_location(Here, _, position(Line0, Char1),
187 _{uri: Here, range: _{start: _{line: Line0, character: Char1},
188 end: _{line: Line1, character: 0}}}) :-
189 !, succ(Line0, Line1).
190relative_ref_location(Here, _, local(Line1),
191 _{uri: Here, range: _{start: _{line: Line0, character: 1},
192 end: _{line: NextLine, character: 0}}}) :-
193 !, succ(Line0, Line1), succ(Line1, NextLine).
194relative_ref_location(_, Goal, imported(Path), Location) :-
195 url_path(ThereUri, Path),
196 xref_source(Path),
197 xref_defined(Path, Goal, Loc),
198 relative_ref_location(ThereUri, Goal, Loc, Location).
204help_at_position(Path, Line1, Char0, S) :-
205 clause_in_file_at_position(Clause, Path, line_char(Line1, Char0)),
206 predicate_help(Path, Clause, S0),
207 maybe_move_path(Path, S0, S1),
208 format_help(S1, S).
217maybe_move_path(Path, Help0, Help) :-
218 string_concat(Path, "\n\n", Path1),
219 string_concat(Path1, Help1, Help0), !,
220 format(string(Help), "~w~n~n~w", [Help1, Path]).
221maybe_move_path(_, Help, Help).
222
223blank_string(S) :-
224 string_codes(S, Cs),
225 phrase(blanks, Cs, []).
230format_help(HelpFull, Help) :-
231 split_string(HelpFull, "\n", " ", Lines0),
232 exclude([Line]>>string_concat("Availability: ", _, Line),
233 Lines0, Lines1),
234 exclude(blank_string, Lines1, Lines2),
235 Lines2 = [HelpShort|_],
236 split_string(HelpFull, "\n", " ", HelpLines),
237 selectchk(HelpShort, HelpLines, "", HelpLines0),
238 append([HelpShort], HelpLines0, HelpLines1),
239 atomic_list_concat(HelpLines1, "\n", Help).
240
241:- if(current_predicate(help_text/2)). 242predicate_help(_, Pred, Help) :- help_text(Pred, Help), !.
243:- else. 244predicate_help(_, Pred, Help) :-
245 nonvar(Pred),
246 prolog_help:help_objects(Pred, exact, Matches), !,
247 catch(prolog_help:help_html(Matches, exact-exact, HtmlDoc), _, fail),
248 setup_call_cleanup(open_string(HtmlDoc, In),
249 load_html(stream(In), Dom, []),
250 close(In)),
251 with_output_to(string(Help), html_text(Dom)).
252:- endif. 253predicate_help(HerePath, Pred, Help) :-
254 xref_source(HerePath),
255 name_callable(Pred, Callable),
256 xref_defined(HerePath, Callable, Loc),
257 location_path(HerePath, Loc, Path),
258 once(xref_comment(Path, Callable, Summary, Comment)),
259 pldoc_process:parse_comment(Comment, Path:0, Parsed),
260 memberchk(mode(Signature, Mode), Parsed),
261 memberchk(predicate(_, Summary, _), Parsed),
262 format(string(Help), " ~w is ~w.~n~n~w", [Signature, Mode, Summary]).
272
273location_path(HerePath, local(_), HerePath).
274location_path(_, imported(Path), Path).
275
276linechar_offset(Stream, line_char(Line1, Char0), Offset) :-
277 seek(Stream, 0, bof, _),
278 seek_to_line(Stream, Line1),
279 seek(Stream, Char0, current, Offset).
280
281seek_to_line(Stream, N) :-
282 N > 1, !,
283 skip(Stream, 0'\n),
284 NN is N - 1,
285 seek_to_line(Stream, NN).
286seek_to_line(_, _).
287
288clause_variable_positions(Path, Line, Variables) :-
289 file_lines_start_end(Path, LineCharRange),
290 read_term_positions(Path, TermsWithPositions),
291 292 file_offset_line_position(LineCharRange, Offset, Line, 0),
293 member(TermInfo, TermsWithPositions),
294 SubTermPoses = TermInfo.subterm,
295 arg(1, SubTermPoses, TermFrom),
296 arg(2, SubTermPoses, TermTo),
297 between(TermFrom, TermTo, Offset), !,
298 find_in_term_with_positions(
299 [X, _]>>( \+ \+ ( X = '$var'(Name), ground(Name) ) ),
300 TermInfo.term,
301 TermInfo.subterm,
302 VariablesPositions, []
303 ),
304 findall(
305 VarName-Locations,
306 group_by(
307 VarName,
308 Location,
309 ( member(found_at('$var'(VarName), Location0-_), VariablesPositions),
310 file_offset_line_position(LineCharRange, Location0, L1, C),
311 succ(L0, L1),
312 Location = position(L0, C)
313 ),
314 Locations
315 ),
316 Variables).
317
318usemod_filespec_position(Path, Line, FileSpec, Position) :-
319 file_lines_start_end(Path, LineCharRange),
320 read_term_positions(Path, TermsWithPositions),
321 322 file_offset_line_position(LineCharRange, Offset, Line, 0),
323 member(TermInfo, TermsWithPositions),
324 SubTermPoses = TermInfo.subterm,
325 arg(1, SubTermPoses, TermFrom),
326 arg(2, SubTermPoses, TermTo),
327 between(TermFrom, TermTo, Offset), !,
328 find_in_term_with_positions(
329 {FileSpec}/[Term, _]>>once(matches_use_module(FileSpec, Term)),
330 TermInfo.term,
331 TermInfo.subterm,
332 Positions,
333 [] ),
334 member(
335 found_at(_Term,
336 term_position(_, _, _, _, [ 337 term_position(_, _, _, _, [ 338 SpecPos | _Rest])])),
339 Positions
340 ),
341 termpos_start_end(SpecPos, Start, End),
342 file_offset_line_position(LineCharRange, Start, StartLine1, StartCol),
343 file_offset_line_position(LineCharRange, End, EndLine1, EndCol),
344 succ(StartLine, StartLine1),
345 succ(EndLine, EndLine1),
346 Position = _{start: _{line: StartLine, character: StartCol},
347 end: _{line: EndLine, character: EndCol } }.
348
349matches_use_module(FileSpec, ( :- use_module(FileSpec) )).
350matches_use_module(FileSpec, ( :- use_module(FileSpec, _) )).
351
352termpos_start_end(From-To, From, To) :- !. 353termpos_start_end(Term, From, To) :-
354 arg(1, Term, From),
355 arg(2, Term, To).
356
357clause_in_file_at_position(Clause, Path, Position) :-
358 xref_source(Path),
359 findall(Op, xref_op(Path, Op), Ops),
360 setup_call_cleanup(
361 open(Path, read, Stream, [ newline(posix) ]),
362 clause_at_position(Stream, Ops, Clause, Position),
363 close(Stream)
364 ).
365
366clause_at_position(Stream, Ops, Clause, Start) :-
367 linechar_offset(Stream, Start, Offset), !,
368 clause_at_position(Stream, Ops, Clause, Start, Offset).
369clause_at_position(Stream, Ops, Clause, line_char(Line1, Char), Here) :-
370 read_source_term_at_location(Stream, Terms, [line(Line1),
371 subterm_positions(SubPos),
372 operators(Ops),
373 error(Error)]),
374 extract_clause_at_position(Stream, Ops, Terms, line_char(Line1, Char), Here,
375 SubPos, Error, Clause).
376
(Stream, Ops, _, line_char(Line1, Char), Here, _,
378 Error, Clause) :-
379 nonvar(Error), !, Line1 > 1,
380 LineBack is Line1 - 1,
381 clause_at_position(Stream, Ops, Clause, line_char(LineBack, Char), Here).
382extract_clause_at_position(_, _, Terms, _, Here, SubPos, _, Clause) :-
383 once(find_clause(Terms, Here, SubPos, Clause)).
389find_clause(Term, Offset, F-T, Clause) :-
390 between(F, T, Offset),
391 ground(Term), Clause = Term/0.
392find_clause(Term, Offset, term_position(_, _, FF, FT, _), Name/Arity) :-
393 between(FF, FT, Offset),
394 functor(Term, Name, Arity).
395find_clause(Term, Offset, term_position(F, T, _, _, SubPoses), Clause) :-
396 between(F, T, Offset),
397 Term =.. [_|SubTerms],
398 find_containing_term(Offset, SubTerms, SubPoses, SubTerm, SubPos),
399 find_clause(SubTerm, Offset, SubPos, Clause).
400find_clause(Term, Offset, parentheses_term_position(F, T, SubPoses), Clause) :-
401 between(F, T, Offset),
402 find_clause(Term, Offset, SubPoses, Clause).
403find_clause({SubTerm}, Offset, brace_term_position(F, T, SubPos), Clause) :-
404 between(F, T, Offset),
405 find_clause(SubTerm, Offset, SubPos, Clause).
406
407find_containing_term(Offset, [Term|_], [F-T|_], Term, F-T) :-
408 between(F, T, Offset).
409find_containing_term(Offset, [Term|_], [P|_], Term, P) :-
410 P = term_position(F, T, _, _, _),
411 between(F, T, Offset), !.
412find_containing_term(Offset, [Term|_], [PP|_], Term, P) :-
413 PP = parentheses_term_position(F, T, P),
414 between(F, T, Offset), !.
415find_containing_term(Offset, [BTerm|_], [BP|_], Term, P) :-
416 BP = brace_term_position(F, T, P),
417 {Term} = BTerm,
418 between(F, T, Offset).
419find_containing_term(Offset, [Terms|_], [LP|_], Term, P) :-
420 LP = list_position(_F, _T, Ps, _),
421 find_containing_term(Offset, Terms, Ps, Term, P).
422find_containing_term(Offset, [Dict|_], [DP|_], Term, P) :-
423 DP = dict_position(_, _, _, _, Ps),
424 member(key_value_position(_F, _T, _SepF, _SepT, Key, _KeyPos, ValuePos),
425 Ps),
426 get_dict(Key, Dict, Value),
427 find_containing_term(Offset, [Value], [ValuePos], Term, P).
428find_containing_term(Offset, [_|Ts], [_|Ps], T, P) :-
429 find_containing_term(Offset, Ts, Ps, T, P).
430
431:- meta_predicate unlimited(//, *, *).
440unlimited(Nonterminal) -->
441 call(Nonterminal),
442 unlimited(Nonterminal)
LSP Utils
Module with a bunch of helper predicates for looking through prolog source and stuff.