1:- module(lsp_formatter, [ file_format_edits/2,
2 file_formatted/2 ]).
12:- use_module(library(readutil), [ read_file_to_string/3 ]). 13:- use_module(library(macros)). 14
15:- include('_lsp_path_add.pl'). 16:- use_module(lsp(lsp_formatter_parser), [ reified_format_for_file/2,
17 emit_reified/2 ]). 18
19file_format_edits(Path, Edits) :-
20 read_file_to_string(Path, OrigText, []),
21 split_string(OrigText, "\n", "", OrigLines),
22 file_formatted(Path, Formatted),
23 with_output_to(string(FormattedText),
24 emit_reified(current_output, Formatted)),
25 split_string(FormattedText, "\n", "", FormattedLines),
26 create_edit_list(OrigLines, FormattedLines, Edits).
27
28file_formatted(Path, Formatted) :-
29 reified_format_for_file(Path, Reified),
30 apply_format_rules(Reified, Formatted).
31
35
36apply_format_rules(Content, Formatted) :-
37 phrase(formatter_rules, Content, Formatted).
38
39formatter_rules -->
40 collapse_whitespace,
41 fact_only_one_space,
42 commas_exactly_one_space,
43 correct_indentation(_{state: [toplevel], column: 0, leading_spaces: [],
44 parens: []}).
45
46collapse_whitespace([], []) :- !.
47collapse_whitespace([white(A), white(B)|InRest], [white(AB)|OutRest]) :- !,
48 AB is A + B,
49 collapse_whitespace(InRest, OutRest).
50collapse_whitespace([In|InRest], [In|OutRest]) :-
51 collapse_whitespace(InRest, OutRest).
52
53fact_only_one_space([], Out) => Out = [].
54fact_only_one_space([term_end(F, toplevel), white(_), Next|InRest], Out), Next \= newline =>
55 Out = [term_end(F, toplevel), white(1), Next|OutRest],
56 fact_only_one_space(InRest, OutRest).
57fact_only_one_space([Other|InRest], Out) =>
58 Out = [Other|OutRest],
59 fact_only_one_space(InRest, OutRest).
60
61commas_exactly_one_space([], Out) => Out = [].
62commas_exactly_one_space([white(_), comma|InRest], Out) =>
63 commas_exactly_one_space([comma|InRest], Out).
64commas_exactly_one_space([comma, white(_)|InRest], Out), InRest \= [comment(_)|_] =>
65 Out = [comma, white(1)|OutRest],
66 commas_exactly_one_space(InRest, OutRest).
67commas_exactly_one_space([comma, Next|InRest], Out), Next \= white(_), Next \= newline =>
68 Out = [comma, white(1), Next|OutRest],
69 commas_exactly_one_space(InRest, OutRest).
70commas_exactly_one_space([Other|Rest], Out) =>
71 Out = [Other|OutRest],
72 commas_exactly_one_space(Rest, OutRest).
73
74#define(toplevel_indent, 4).
75
76correct_indentation(_, [], []) :- !.
77correct_indentation(State0,
78 [term_begin(Func, Type, Parens)|InRest],
79 [term_begin(Func, Type, Parens)|OutRest]) :-
80 indent_state_top(State0, toplevel),
81 Func = ':-', !,
82 indent_state_push(State0, declaration, State1),
83 update_state_column(State1, term_begin(Func, Type, Parens), State2),
84 push_state_open_spaces(State2, InRest, State3),
85 correct_indentation(State3, InRest, OutRest).
86correct_indentation(State0,
87 [term_begin(Func, Type, Parens)|InRest],
88 [term_begin(Func, Type, Parens)|OutRest]) :-
89 indent_state_top(State0, toplevel), !,
90 update_state_column(State0, term_begin(Func, Type, Parens), State1),
91 indent_state_push(State1, defn_head(State1.column, false), State2),
92 push_state_open_spaces(State2, InRest, State3),
93 correct_indentation(State3, InRest, OutRest).
94correct_indentation(State0, [In|InRest], [In|OutRest]) :-
95 indent_state_top(State0, toplevel),
96 In = simple(_), !,
97 indent_state_push(State0, defn_head_neck, State1),
98 update_state_column(State1, In, State2),
99 correct_indentation(State2, InRest, OutRest).
100correct_indentation(State0,
101 [term_begin(Neckish, T, P)|InRest],
102 [term_begin(Neckish, T, P)|OutRest]) :-
103 memberchk(Neckish, [':-', '=>', '-->']),
104 indent_state_top(State0, defn_head_neck), !,
105 indent_state_pop(State0, State1),
106 indent_state_push(State1, defn_body, State2),
107 update_state_column(State2, term_begin(Neckish, T, P), State3),
108 push_state_open_spaces(State3, InRest, State4),
109 correct_indentation(State4, InRest, OutRest).
110correct_indentation(State0, [In|InRest], Out) :-
111 once((In = term_begin('->', compound, false)
112 ; In = term_begin('*->', compound, false)
113 ; In = term_begin(';', compound, false))),
114 indent_state_top(State0, defn_body_indent), !,
115 indent_state_pop(State0, State1),
116 paren_state_top(State1, Indent),
117 Out = [white(Indent)|OutRest],
118 update_state_column(State1, white(Indent), State4),
119 correct_indentation(State4, [In|InRest], OutRest).
120correct_indentation(State0, [newline|InRest], [newline|Out]) :- !,
121 ( indent_state_top(State0, defn_body_indent)
122 -> State1 = State0
123 ; indent_state_push(State0, defn_body_indent, State1) ),
124 update_state_column(State1, newline, State2),
125 correct_indentation(State2, InRest, Out).
126correct_indentation(State0, [In|InRest], Out) :-
127 indent_state_top(State0, defn_body_indent), !,
128 ( In = white(_)
129 -> correct_indentation(State0, InRest, Out)
130 ; insert_whitespace_to_indent(State0, [In|InRest], Out) ).
131correct_indentation(State0, [In|InRest], [In|OutRest]) :-
132 In = term_begin(';', compound, false), !,
133 update_alignment(State0, State1),
134 update_state_column(State1, In, State2),
136 copy_current_alignment(State2, CurrentAlign),
137 indent_state_push(State2, CurrentAlign, State3),
138 push_state_open_spaces(State3, InRest, State4),
139 correct_indentation(State4, InRest, OutRest)
139.
140correct_indentation(State0, [In|InRest], [In|OutRest]) :-
141 functor(In, Name, _Arity, _Type),
142 atom_concat(_, '_begin', Name), !,
143 144 update_alignment(State0, State1),
145 update_state_column(State1, In, State2),
146 indent_state_push(State2, begin(State2.column, State1.column), State3),
147 push_state_open_spaces(State3, InRest, State4),
148 ( Name == parens_begin
149 -> paren_state_push(State4, State1.column, State5)
150 ; State5 = State4 ),
151 correct_indentation(State5, InRest, OutRest).
152correct_indentation(State0, [In|InRest], [In|OutRest]) :-
153 indent_state_top(State0, defn_head(_, _)),
154 In = term_end(_, S), S \= toplevel, !,
155 indent_state_pop(State0, State1),
156 indent_state_push(State1, defn_head_neck, State2),
157 update_state_column(State2, In, State3),
158 pop_state_open_spaces(State3, _, State4),
159 correct_indentation(State4, InRest, OutRest).
160correct_indentation(State0, [In|InRest], Out) :-
161 ending_term(In), !,
162 indent_state_pop(State0, State1),
163 update_state_column(State1, In, State2),
164 pop_state_open_spaces(State2, Spaces, State3),
165 ( In \= term_end(false, _), In \= term_end(_, toplevel), Spaces > 0
166 -> Out = [white(Spaces), In|OutRest]
167 ; Out = [In|OutRest] ),
168 ( In == parens_end
169 -> paren_state_pop(State3, State4)
170 ; State3 = State4 ),
171 correct_indentation(State4, InRest, OutRest).
172correct_indentation(State0, [In, NextIn|InRest], Out) :-
173 In = white(_),
174 ending_term(NextIn), !,
175 correct_indentation(State0, [NextIn|InRest], Out).
176correct_indentation(State0, [In|InRest], [In|OutRest]) :-
177 memberchk(In, [white(_), newline]), !,
178 update_state_column(State0, In, State1),
179 correct_indentation(State1, InRest, OutRest).
180correct_indentation(State0, [In|InRest], [In|OutRest]) :- !,
181 ( In \= white(_)
182 -> update_alignment(State0, State1)
183 ; State1 = State0 ),
184 update_state_column(State1, In, State2),
185 correct_indentation(State2, InRest, OutRest).
186
187copy_current_alignment(State, Alignment), indent_state_top(State, defn_body) =>
188 Alignment = align(#toplevel_indent, 4).
189copy_current_alignment(State, Alignment), indent_state_top(State, align(_, _)) =>
190 indent_state_top(State, Alignment).
191copy_current_alignment(State, Alignment), indent_state_top(State, begin(Col, BeganAt)) =>
192 Alignment = begin(Col, BeganAt).
193copy_current_alignment(State, Alignment), indent_state_top(State, defn_body_indent) =>
194 Alignment = align(#toplevel_indent, 4).
195copy_current_alignment(State, Alignment), indent_state_top(State, defn_head(Column, _Aligned)) =>
196 Alignment = align(Column, Column).
197copy_current_alignment(State, Alignment), indent_state_top(State, defn_head_neck) =>
198 Alignment = align(#toplevel_indent, 4).
199copy_current_alignment(State, Alignment), indent_state_top(State, declaration) =>
200 Alignment = align(2, 2).
201copy_current_alignment(State, Alignment) =>
202 indent_state_top(State, Alignment).
203
204insert_whitespace_to_indent(State0, [In|InRest], Out) :-
205 indent_state_pop(State0, State1),
206 ( indent_state_top(State1, begin(_, BeganAt))
207 208 -> 209 indent_state_pop(State1, StateX),
210 whitespace_indentation_for_state(StateX, PrevIndent),
211 IncPrevIndent is PrevIndent + 4,
212 indent_state_push(StateX, align(IncPrevIndent, BeganAt), State2)
213 ; State2 = State1 ),
214 update_alignment(State2, State3),
215 ( ending_term(In)
216 -> indent_for_end_term(State3, In, State4, Indent)
217 ; whitespace_indentation_for_state(State3, Indent),
218 State4 = State3 ),
219 Out = [white(Indent)|OutRest],
220 update_state_column(State4, white(Indent), State5),
221 correct_indentation(State5, [In|InRest], OutRest).
222
223indent_for_end_term(State0, In, State, Indent) :-
224 225 In = term_end(true, _), !,
226 indent_state_pop(State0, State_),
227 pop_state_open_spaces(State0, _, State1),
228 push_state_open_spaces(State1, 0, State),
229 whitespace_indentation_for_state(State_, Indent).
230indent_for_end_term(State0, In, State, Indent) :-
231 232 In = dict_end, !,
233 indent_state_pop(State0, State_),
234 indent_state_pop(State_, State__),
235 pop_state_open_spaces(State0, _, State1),
236 push_state_open_spaces(State1, 0, State),
237 whitespace_indentation_for_state(State__, Indent).
238indent_for_end_term(State0, _In, State, Indent) :-
239 240 241 indent_state_top(State0, Top),
242 Top = align(_, Indent), !,
243 pop_state_open_spaces(State0, _, State1),
244 push_state_open_spaces(State1, 0, State).
245indent_for_end_term(State0, _In, State, Indent) :-
246 247 indent_state_pop(State0, State_),
248 pop_state_open_spaces(State0, _, State1),
249 push_state_open_spaces(State1, 0, State),
250 whitespace_indentation_for_state(State_, Indent).
251
252ending_term(Term) :-
253 functor(Term, Name, _, _),
254 atom_concat(_, '_end', Name).
255
256outdent_align(State, Outdented) :-
257 whitespace_indentation_for_state(State, Indent),
258 Outdented is Indent - 2.
259
260update_alignment(State0, State2) :-
261 indent_state_top(State0, begin(Col, BeganAt)), !,
262 indent_state_pop(State0, State1),
263 AlignCol is max(Col, State1.column),
264 indent_state_push(State1, align(AlignCol, BeganAt), State2).
265update_alignment(State0, State2) :-
266 indent_state_top(State0, defn_head(Col, false)), !,
267 indent_state_pop(State0, State1),
268 AlignCol is max(Col, State1.column),
269 indent_state_push(State1, defn_head(AlignCol, true), State2).
270update_alignment(State, State).
271
272whitespace_indentation_for_state(State, Indent) :-
273 indent_state_top(State, align(Indent, _)), !.
274whitespace_indentation_for_state(State, Indent) :-
275 indent_state_top(State, defn_head(Indent, _)), !.
276whitespace_indentation_for_state(State, Indent) :-
277 get_dict(state, State, Stack),
278 aggregate_all(count,
279 ( member(X, Stack),
280 memberchk(X, [parens_begin, braces_begin, term_begin(_, _, _)]) ),
281 ParensCount),
282 ( indent_state_contains(State, defn_body)
283 -> MoreIndent = #toplevel_indent
284 ; MoreIndent = 0 ),
285 Indent is ParensCount * 2 + MoreIndent.
286
287indent_state_top(State, Top) :-
288 _{state: [Top|_]} :< State.
289
290indent_state_contains(State, Needle) :-
291 _{state: Stack} :< State,
292 memberchk(Needle, Stack).
293
294paren_state_push(State0, NewTop, State1) :-
295 _{parens: OldParens} :< State0,
296 put_dict(parens, State0, [NewTop|OldParens], State1).
297
298paren_state_pop(State0, State1) :-
299 _{parens: [_|Parens]} :< State0,
300 put_dict(parens, State0, Parens, State1).
301
302paren_state_top(State0, Top) :-
303 _{parens: [Top|_]} :< State0.
304
305indent_state_push(State0, NewTop, State1) :-
306 _{state: Stack} :< State0,
307 put_dict(state, State0, [NewTop|Stack], State1).
308
309indent_state_pop(State0, State1) :-
310 _{state: [_|Rest]} :< State0,
311 put_dict(state, State0, Rest, State1).
312
313update_state_column(State0, newline, State1) :- !,
314 put_dict(column, State0, 0, State1).
315update_state_column(State0, Term, State1) :-
316 emit_reified(string(S), [Term]),
317 string_length(S, Len),
318 NewCol is State0.column + Len,
319 put_dict(column, State0, NewCol, State1).
320
321push_state_open_spaces(State0, Next, State1) :-
322 _{leading_spaces: PrevSpaces} :< State0,
323 ( Next = [white(N)|_]
324 -> put_dict(leading_spaces, State0, [N|PrevSpaces], State1)
325 ; put_dict(leading_spaces, State0, [0|PrevSpaces], State1) ).
326
327pop_state_open_spaces(State0, Top, State1) :-
328 _{leading_spaces: [Top|Spaces]} :< State0,
329 put_dict(leading_spaces, State0, Spaces, State1).
330
334create_edit_list(Orig, Formatted, Edits) :-
335 create_edit_list(0, Orig, Formatted, Edits).
336
337create_edit_list(_, [], [], []) :- !.
338create_edit_list(LineNum, [Line|Lines], [], [Edit]) :- !,
339 length(Lines, NLines),
340 EndLine is LineNum + NLines,
341 last([Line|Lines], LastLine),
342 string_length(LastLine, LastLineLen),
343 Edit = _{range: _{start: _{line: LineNum, character: 0},
344 end: _{line: EndLine, character: LastLineLen}},
345 newText: ""}.
346create_edit_list(LineNum, [], [NewLine|NewLines], [Edit|Edits]) :- !,
347 string_length(NewLine, LenLen),
348 Edit = _{range: _{start: _{line: LineNum, character: 0},
349 end: _{line: LineNum, character: LenLen}},
350 newText: NewLine},
351 succ(LineNum, LineNum1),
352 create_edit_list(LineNum1, [], NewLines, Edits).
353create_edit_list(LineNum, [OrigLine|OrigRest], [FormattedLine|FormattedRest], Edits) :-
354 ( OrigLine \= FormattedLine 355 -> string_length(OrigLine, LineLen), 356 Edit = _{range: _{start: _{line: LineNum, character: 0},
357 end: _{line: LineNum, character: LineLen}},
358 newText: FormattedLine},
359 Edits = [Edit|EditRest]
360 ; EditRest = Edits
361 ),
362 succ(LineNum, LineNum1),
363 create_edit_list(LineNum1, OrigRest, FormattedRest, EditRest).
364
366
LSP Formatter
Module for formatting Prolog source code