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 seek_to_line/2,
9 linechar_offset/3,
10 url_path/2
11 ]).
20:- use_module(library(apply_macros)). 21:- use_module(library(apply), [maplist/3, exclude/3]). 22:- use_module(library(prolog_xref)). 23:- use_module(library(prolog_source), [read_source_term_at_location/3]). 24:- use_module(library(help), [help_html/3, help_objects/3]). 25:- use_module(library(lynx/html_text), [html_text/1]). 26:- use_module(library(solution_sequences), [distinct/2]). 27:- use_module(library(lists), [append/3, member/2, selectchk/4]). 28:- use_module(library(sgml), [load_html/3]). 29:- use_module(library(yall)). 30
31:- include('_lsp_path_add.pl'). 32
33:- use_module(lsp(lsp_reading_source), [ file_lines_start_end/2,
34 read_term_positions/2,
35 read_term_positions/4,
36 find_in_term_with_positions/5,
37 position_to_match/3,
38 file_offset_line_position/4 ]). 39
40:- if(current_predicate(xref_called/5)).
45called_at(Path, Clause, Locations) :-
46 setof(L, Path^Clause^Locs^(
47 called_at_(Path, Clause, Locs),
48 member(L, Locs)
49 ),
50 Locations), !.
51called_at(Path, Clause, Locations) :-
52 name_callable(Clause, Callable),
53 xref_source(Path),
54 xref_called(Path, Callable, _By, _, CallerLine),
55 56 succ(CallerLine0, CallerLine),
57 Locations = [_{range: _{start: _{line: CallerLine0, character: 0},
58 end: _{line: CallerLine, character: 0}}}].
59
60called_at_(Path, Clause, Locations) :-
61 name_callable(Clause, Callable),
62 xref_source(Path),
63 xref_called(Path, Callable, _By, _, CallerLine),
64 file_lines_start_end(Path, LineCharRange),
65 file_offset_line_position(LineCharRange, Offset, CallerLine, 0),
66 read_term_positions(Path, Offset, Offset, TermInfos),
67 Clause = FuncName/Arity,
68 find_occurences_of_callable(Path, FuncName, Arity, TermInfos, Matches, []),
69 maplist(position_to_match(LineCharRange), Matches, Locations).
70called_at_(Path, Clause, Locations) :-
71 xref_source(Path),
72 Clause = FuncName/Arity,
73 DcgArity is Arity + 2,
74 DcgClause = FuncName/DcgArity,
75 name_callable(DcgClause, DcgCallable),
76 xref_defined(Path, DcgCallable, dcg),
77 name_callable(DcgClause, DcgCallable),
78 xref_called(Path, DcgCallable, _By, _, CallerLine),
79 file_lines_start_end(Path, LineCharRange),
80 file_offset_line_position(LineCharRange, Offset, CallerLine, 0),
81 read_term_positions(Path, Offset, Offset, TermInfos),
82 find_occurences_of_callable(Path, FuncName, DcgArity, TermInfos, Matches, Tail0),
83 84 85 86 find_occurences_of_callable(Path, FuncName, Arity, TermInfos, Tail0, []),
87 maplist(position_to_match(LineCharRange), Matches, Locations).
88:- else. 89called_at(Path, Callable, By, Ref) :-
90 xref_called(Path, Callable, By),
91 xref_defined(Path, By, Ref).
92:- endif. 93
94find_occurences_of_callable(_, _, _, [], Tail, Tail).
95find_occurences_of_callable(Path, FuncName, Arity, [TermInfo|TermInfos], Matches, Tail) :-
96 FindState = in_meta(false),
97 find_in_term_with_positions(term_matches_callable(FindState, Path, FuncName, Arity),
98 TermInfo.term, TermInfo.subterm, Matches, Tail0),
99 find_occurences_of_callable(Path, FuncName, Arity, TermInfos, Tail0, Tail).
100
101term_matches_callable(FindState, Path, FuncName, Arity, Term, Position) :-
102 arg(1, Position, Start),
103 arg(2, Position, End),
104 ( arg(1, FindState, in_meta(_, MStart, MEnd)),
105 once( Start > MEnd ; End < MStart )
106 -> nb_setarg(1, FindState, false)
107 ; true ),
108 term_matches_callable_(FindState, Path, FuncName, Arity, Term, Position).
109
110term_matches_callable_(_, _, FuncName, Arity, Term, _) :-
111 nonvar(Term), Term = FuncName/Arity.
112term_matches_callable_(_, _, FuncName, Arity, Term, _) :-
113 nonvar(Term),
114 functor(T, FuncName, Arity),
115 Term = T, !.
116term_matches_callable_(State, _, FuncName, Arity, Term, _) :-
117 nonvar(Term),
118 119 arg(1, State, in_meta(N, _, _)),
120 MArity is Arity - N,
121 functor(T, FuncName, MArity),
122 Term = T, !.
123term_matches_callable_(State, Path, _, _, Term, Position) :-
124 nonvar(Term), compound(Term),
125 compound_name_arity(Term, ThisName, ThisArity),
126 name_callable(ThisName/ThisArity, Callable),
127 xref_meta(Path, Callable, Called),
128 member(E, Called), nonvar(E), E = _+N, integer(N),
129 arg(1, Position, Start),
130 arg(2, Position, End),
131 nb_setarg(1, State, in_meta(N, Start, End)),
132 fail.
137url_path(Url, Path) :-
138 current_prolog_flag(windows, true),
139 140 141 142 atom_concat('file:///', Path, Url), !.
143url_path(Url, Path) :-
144 atom_concat('file://', Path, Url).
145
146defined_at(Path, Name/Arity, Location) :-
147 name_callable(Name/Arity, Callable),
148 xref_source(Path),
149 xref_defined(Path, Callable, Ref),
150 url_path(Doc, Path),
151 relative_ref_location(Doc, Callable, Ref, Location).
152defined_at(Path, Name/Arity, Location) :-
153 154 DcgArity is Arity + 2,
155 name_callable(Name/DcgArity, Callable),
156 xref_source(Path),
157 xref_defined(Path, Callable, Ref),
158 url_path(Doc, Path),
159 relative_ref_location(Doc, Callable, Ref, Location).
160
161
162find_subclause(Stream, Subclause, CallerLine, Locations) :-
163 read_source_term_at_location(Stream, Term, [line(CallerLine),
164 subterm_positions(Poses)]),
165 findall(Offset, distinct(Offset, find_clause(Term, Offset, Poses, Subclause)),
166 Offsets),
167 collapse_adjacent(Offsets, StartOffsets),
168 maplist(offset_line_char(Stream), StartOffsets, Locations).
169
170offset_line_char(Stream, Offset, position(Line, Char)) :-
171 172 173 set_stream_position(Stream, '$stream_position'(0, 0, 0, 0)),
174 setup_call_cleanup(
175 open_null_stream(NullStream),
176 copy_stream_data(Stream, NullStream, Offset),
177 close(NullStream)
178 ),
179 stream_property(Stream, position(Pos)),
180 stream_position_data(line_count, Pos, Line),
181 stream_position_data(line_position, Pos, Char).
182
183collapse_adjacent([X|Rst], [X|CRst]) :-
184 collapse_adjacent(X, Rst, CRst).
185collapse_adjacent(X, [Y|Rst], CRst) :-
186 succ(X, Y), !,
187 collapse_adjacent(Y, Rst, CRst).
188collapse_adjacent(_, [X|Rst], [X|CRst]) :- !,
189 collapse_adjacent(X, Rst, CRst).
190collapse_adjacent(_, [], []).
196name_callable(Name/0, Name) :- atom(Name), !.
197name_callable(Name/Arity, Callable) :-
198 length(FakeArgs, Arity),
199 Callable =.. [Name|FakeArgs], !.
205relative_ref_location(Here, _, position(Line0, Char1),
206 _{uri: Here, range: _{start: _{line: Line0, character: Char1},
207 end: _{line: Line1, character: 0}}}) :-
208 !, succ(Line0, Line1).
209relative_ref_location(Here, _, local(Line1),
210 _{uri: Here, range: _{start: _{line: Line0, character: 1},
211 end: _{line: NextLine, character: 0}}}) :-
212 !, succ(Line0, Line1), succ(Line1, NextLine).
213relative_ref_location(_, Goal, imported(Path), Location) :-
214 url_path(ThereUri, Path),
215 xref_source(Path),
216 xref_defined(Path, Goal, Loc),
217 relative_ref_location(ThereUri, Goal, Loc, Location).
223help_at_position(Path, Line1, Char0, S) :-
224 clause_in_file_at_position(Clause, Path, line_char(Line1, Char0)),
225 predicate_help(Path, Clause, S0),
226 format_help(S0, S).
231format_help(HelpFull, Help) :-
232 split_string(HelpFull, "\n", " ", Lines0),
233 exclude([Line]>>string_concat("Availability: ", _, Line),
234 Lines0, Lines1),
235 exclude(=(""), Lines1, Lines2),
236 Lines2 = [HelpShort|_],
237 split_string(HelpFull, "\n", " ", HelpLines),
238 selectchk(HelpShort, HelpLines, "", HelpLines0),
239 append([HelpShort], HelpLines0, HelpLines1),
240 atomic_list_concat(HelpLines1, "\n", Help).
241
242predicate_help(_, Pred, Help) :-
243 nonvar(Pred),
244 help_objects(Pred, exact, Matches), !,
245 catch(help_html(Matches, exact-exact, HtmlDoc), _, fail),
246 setup_call_cleanup(open_string(HtmlDoc, In),
247 load_html(stream(In), Dom, []),
248 close(In)),
249 with_output_to(string(Help), html_text(Dom)).
250predicate_help(HerePath, Pred, Help) :-
251 xref_source(HerePath),
252 name_callable(Pred, Callable),
253 xref_defined(HerePath, Callable, Loc),
254 location_path(HerePath, Loc, Path),
255 once(xref_comment(Path, Callable, Summary, Comment)),
256 pldoc_process:parse_comment(Comment, Path:0, Parsed),
257 memberchk(mode(Signature, Mode), Parsed),
258 memberchk(predicate(_, Summary, _), Parsed),
259 format(string(Help), " ~w is ~w.~n~n~w", [Signature, Mode, Summary]).
260predicate_help(_, Pred/_Arity, Help) :-
261 help_objects(Pred, dwim, Matches), !,
262 catch(help_html(Matches, dwim-Pred, HtmlDoc), _, fail),
263 setup_call_cleanup(open_string(HtmlDoc, In),
264 load_html(stream(In), Dom, []),
265 close(In)),
266 with_output_to(string(Help), html_text(Dom)).
267
268location_path(HerePath, local(_), HerePath).
269location_path(_, imported(Path), Path).
270
271linechar_offset(Stream, line_char(Line1, Char0), Offset) :-
272 seek(Stream, 0, bof, _),
273 seek_to_line(Stream, Line1),
274 seek(Stream, Char0, current, Offset).
275
276seek_to_line(Stream, N) :-
277 N > 1, !,
278 skip(Stream, 0'\n),
279 NN is N - 1,
280 seek_to_line(Stream, NN).
281seek_to_line(_, _).
282
283clause_variable_positions(Path, Line, Variables) :-
284 file_lines_start_end(Path, LineCharRange),
285 read_term_positions(Path, TermsWithPositions),
286 287 file_offset_line_position(LineCharRange, Offset, Line, 0),
288 member(TermInfo, TermsWithPositions),
289 SubTermPoses = TermInfo.subterm,
290 arg(1, SubTermPoses, TermFrom),
291 arg(2, SubTermPoses, TermTo),
292 between(TermFrom, TermTo, Offset), !,
293 find_in_term_with_positions(
294 [X, _]>>( \+ \+ ( X = '$var'(Name), ground(Name) ) ),
295 TermInfo.term,
296 TermInfo.subterm,
297 VariablesPositions, []
298 ),
299 findall(
300 VarName-Locations,
301 group_by(
302 VarName,
303 Location,
304 ( member(found_at('$var'(VarName), Location0-_), VariablesPositions),
305 file_offset_line_position(LineCharRange, Location0, L1, C),
306 succ(L0, L1),
307 Location = position(L0, C)
308 ),
309 Locations
310 ),
311 Variables).
312
313clause_in_file_at_position(Clause, Path, Position) :-
314 xref_source(Path),
315 findall(Op, xref_op(Path, Op), Ops),
316 setup_call_cleanup(
317 open(Path, read, Stream, []),
318 clause_at_position(Stream, Ops, Clause, Position),
319 close(Stream)
320 ).
321
322clause_at_position(Stream, Ops, Clause, Start) :-
323 linechar_offset(Stream, Start, Offset), !,
324 clause_at_position(Stream, Ops, Clause, Start, Offset).
325clause_at_position(Stream, Ops, Clause, line_char(Line1, Char), Here) :-
326 read_source_term_at_location(Stream, Terms, [line(Line1),
327 subterm_positions(SubPos),
328 operators(Ops),
329 error(Error)]),
330 extract_clause_at_position(Stream, Ops, Terms, line_char(Line1, Char), Here,
331 SubPos, Error, Clause).
332
(Stream, Ops, _, line_char(Line1, Char), Here, _,
334 Error, Clause) :-
335 nonvar(Error), !, Line1 > 1,
336 LineBack is Line1 - 1,
337 clause_at_position(Stream, Ops, Clause, line_char(LineBack, Char), Here).
338extract_clause_at_position(_, _, Terms, _, Here, SubPos, _, Clause) :-
339 once(find_clause(Terms, Here, SubPos, Clause)).
345find_clause(Term, Offset, F-T, Clause) :-
346 between(F, T, Offset),
347 ground(Term), Clause = Term/0.
348find_clause(Term, Offset, term_position(_, _, FF, FT, _), Name/Arity) :-
349 between(FF, FT, Offset),
350 functor(Term, Name, Arity).
351find_clause(Term, Offset, term_position(F, T, _, _, SubPoses), Clause) :-
352 between(F, T, Offset),
353 Term =.. [_|SubTerms],
354 find_containing_term(Offset, SubTerms, SubPoses, SubTerm, SubPos),
355 find_clause(SubTerm, Offset, SubPos, Clause).
356find_clause(Term, Offset, parentheses_term_position(F, T, SubPoses), Clause) :-
357 between(F, T, Offset),
358 find_clause(Term, Offset, SubPoses, Clause).
359find_clause({SubTerm}, Offset, brace_term_position(F, T, SubPos), Clause) :-
360 between(F, T, Offset),
361 find_clause(SubTerm, Offset, SubPos, Clause).
362
363find_containing_term(Offset, [Term|_], [F-T|_], Term, F-T) :-
364 between(F, T, Offset).
365find_containing_term(Offset, [Term|_], [P|_], Term, P) :-
366 P = term_position(F, T, _, _, _),
367 between(F, T, Offset), !.
368find_containing_term(Offset, [Term|_], [PP|_], Term, P) :-
369 PP = parentheses_term_position(F, T, P),
370 between(F, T, Offset), !.
371find_containing_term(Offset, [BTerm|_], [BP|_], Term, P) :-
372 BP = brace_term_position(F, T, P),
373 {Term} = BTerm,
374 between(F, T, Offset).
375find_containing_term(Offset, [Terms|_], [LP|_], Term, P) :-
376 LP = list_position(_F, _T, Ps, _),
377 find_containing_term(Offset, Terms, Ps, Term, P).
378find_containing_term(Offset, [Dict|_], [DP|_], Term, P) :-
379 DP = dict_position(_, _, _, _, Ps),
380 member(key_value_position(_F, _T, _SepF, _SepT, Key, _KeyPos, ValuePos),
381 Ps),
382 get_dict(Key, Dict, Value),
383 find_containing_term(Offset, [Value], [ValuePos], Term, P).
384find_containing_term(Offset, [_|Ts], [_|Ps], T, P) :-
385 find_containing_term(Offset, Ts, Ps, T, P)
LSP Utils
Module with a bunch of helper predicates for looking through prolog source and stuff.