Merge: introduce nit_env.sh to setup the shell environement
[nit.git] / src / doc / vim_autocomplete.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Generate files used by the Vim plugin to autocomplete with doc
16 #
17 # There is 3 files generated, each with a different target: modules, types,
18 # properties and constructors. Each line describe a different entity,
19 # with 4 values:
20 #
21 # 1. Short name to use in autocompletion
22 # 2. Full signature
23 # 3. Doc synopsis
24 # 4. Full doc with extra
25 #
26 # The priority with those files is for them to be analyzed efficiently, for
27 # this reason, the data is prepared in advance and some information may be
28 # duplicated.
29 module vim_autocomplete
30
31 import modelbuilder
32 import phase
33 import modelize::modelize_class
34 import model::model_collect
35
36 redef class ToolContext
37 # Phase generating the files for the Vim plugin
38 var autocomplete_phase: Phase = new AutocompletePhase(self, [modelize_class_phase])
39
40 # Shall we generate the files for the Vim plugin?
41 var opt_vim_autocomplete = new OptionBool(
42 "Generate metadata files used by the Vim plugin for autocompletion", "--vim-autocomplete")
43
44 init
45 do
46 super
47 option_context.add_option opt_vim_autocomplete
48 opt_vim_autocomplete.hidden = true
49 end
50 end
51
52 redef class MEntity
53 private fun field_separator: String do return "#====#"
54 private fun line_separator: String do return "#nnnn#"
55
56 private fun write_doc(mainmodule: MModule, stream: Writer)
57 do
58 # 1. Short name for autocompletion
59 stream.write complete_name
60 stream.write field_separator
61
62 # 2. Full signature
63 stream.write complete_name
64 write_signature_to_stream(stream)
65 stream.write field_separator
66
67 # 3. Doc synopsis
68 var mdoc = complete_mdoc
69 if mdoc != null then
70 stream.write mdoc.content.first
71 end
72
73 # 4. Full doc with extra
74 stream.write field_separator
75 stream.write "# "
76 stream.write full_name
77 write_signature_to_stream(stream)
78 if mdoc != null then
79 for i in 2.times do stream.write line_separator
80 stream.write mdoc.content.join(line_separator)
81 end
82 write_extra_doc(mainmodule, stream)
83
84 stream.write "\n"
85 end
86
87 private fun write_signature_to_stream(stream: Writer) do end
88
89 # Actual name used in completion
90 private fun complete_name: String do return name
91
92 # Doc to use in completion
93 private fun complete_mdoc: nullable MDoc do return mdoc
94
95 # Extra auto documentation to append to the `stream`
96 private fun write_extra_doc(mainmodule: MModule, stream: Writer) do end
97 end
98
99 redef class MMethodDef
100 redef fun write_signature_to_stream(stream)
101 do
102 var msignature = msignature
103 if msignature != null then
104 stream.write msignature.to_s
105 end
106 end
107 end
108
109 redef class MAttributeDef
110 redef fun write_signature_to_stream(stream)
111 do
112 var static_mtype = static_mtype
113 if static_mtype != null then
114 stream.write stream.to_s
115 end
116 end
117 end
118
119 # Use `MClassDef` as anchor for its constructors only
120 redef class MClassDef
121 private var target_constructor: nullable MMethodDef = null
122
123 redef fun complete_name
124 do
125 var target_constructor = target_constructor
126 assert target_constructor != null
127
128 var params
129 var mparameters = mclass.mparameters
130 if not mparameters.is_empty then
131 params = "[{mparameters.join(", ")}]"
132 else
133 params = ""
134 end
135
136 if target_constructor.name != "init" and target_constructor.name != "new" then
137 return name + params + "." + target_constructor.name
138 end
139
140 return name + params
141 end
142
143 redef fun complete_mdoc
144 do
145 var target_constructor = target_constructor
146 assert target_constructor != null
147
148 if target_constructor.name != "init" and target_constructor.name != "new" then
149 return target_constructor.mdoc
150 end
151
152 return mdoc
153 end
154 end
155
156 redef class MClassType
157 redef fun write_extra_doc(mainmodule, stream)
158 do
159 # Super classes
160 stream.write line_separator*2
161 stream.write "## Class hierarchy"
162
163 var direct_supers = [for s in mclass.in_hierarchy(mainmodule).direct_greaters do s.name]
164 if not direct_supers.is_empty then
165 alpha_comparator.sort direct_supers
166 stream.write line_separator
167 stream.write "* Direct super classes: "
168 stream.write direct_supers.join(", ")
169 end
170
171 var supers = [for s in mclass.in_hierarchy(mainmodule).greaters do s.name]
172 supers.remove mclass.name
173 if not supers.is_empty then
174 alpha_comparator.sort supers
175 stream.write line_separator
176 stream.write "* All super classes: "
177 stream.write supers.join(", ")
178 end
179
180 var direct_subs = [for s in mclass.in_hierarchy(mainmodule).direct_smallers do s.name]
181 if not direct_subs.is_empty then
182 alpha_comparator.sort direct_subs
183 stream.write line_separator
184 stream.write "* Direct sub classes: "
185 stream.write direct_subs.join(", ")
186 end
187
188 var subs = [for s in mclass.in_hierarchy(mainmodule).smallers do s.name]
189 subs.remove mclass.name
190 if not subs.is_empty then
191 alpha_comparator.sort subs
192 stream.write line_separator
193 stream.write "* All sub classes: "
194 stream.write subs.join(", ")
195 end
196
197 # List other properties
198 stream.write line_separator*2
199 stream.write "## Properties"
200 stream.write line_separator
201 var props = mclass.collect_accessible_mproperties(protected_visibility).to_a
202 alpha_comparator.sort props
203 for prop in props do
204 if mclass.name == "Object" or prop.intro.mclassdef.mclass.name != "Object" then
205 prop.write_synopsis(mainmodule, stream)
206 end
207 end
208 end
209
210 redef fun complete_mdoc do return mclass.intro.mdoc
211 end
212
213 private class AutocompletePhase
214 super Phase
215
216 redef fun process_mainmodule(mainmodule, given_mmodules)
217 do
218 if not toolcontext.opt_vim_autocomplete.value then return
219
220 var compile_dir = "NIT_VIM_DIR".environ
221 if compile_dir.is_empty then compile_dir = "HOME".environ / ".vim/nit"
222 compile_dir.mkdir
223
224 var modules_stream = new FileWriter.open(compile_dir / "modules.txt")
225 var classes_stream = new FileWriter.open(compile_dir / "classes.txt")
226 var constructors_stream = new FileWriter.open(compile_dir / "constructors.txt")
227 var types_stream = new FileWriter.open(compile_dir / "types.txt")
228 var properties_stream = new FileWriter.open(compile_dir / "properties.txt")
229
230 # Got all known modules
231 var model = mainmodule.model
232 for mmodule in model.mmodules do
233 mmodule.write_doc(mainmodule, modules_stream)
234 end
235
236 # TODO list other modules from the Nit lib
237
238 # Get all known classes
239 for mclass in model.mclasses do
240 if not mainmodule.is_visible(mclass.intro_mmodule, public_visibility) then continue
241 var mclass_intro = mclass.intro
242
243 # Can it be instantiated?
244 if mclass.kind != interface_kind and mclass.kind != abstract_kind then
245
246 for prop in mclass.collect_accessible_mproperties(public_visibility) do
247 if prop isa MMethod and prop.is_init then
248 mclass_intro.target_constructor = prop.intro
249 mclass_intro.write_doc(mainmodule, constructors_stream)
250 end
251 end
252 mclass_intro.target_constructor = null
253 end
254
255 # Always add to types and classes
256 mclass.mclass_type.write_doc(mainmodule, classes_stream)
257 mclass.mclass_type.write_doc(mainmodule, types_stream)
258 end
259
260 # Get all known properties
261 for mproperty in model.mproperties do
262 var intro_mmodule = mproperty.intro_mclassdef.mmodule
263 if not mainmodule.is_visible(intro_mmodule, public_visibility) then continue
264
265 # Is it a virtual type?
266 if mproperty isa MVirtualTypeProp then
267 mproperty.intro.write_doc(mainmodule, types_stream)
268 continue
269 end
270
271 # Skip properties beginning with @ or _
272 var first_letter = mproperty.name.chars.first
273 if first_letter == '@' or first_letter == '_' then continue
274
275 mproperty.intro.write_doc(mainmodule, properties_stream)
276 end
277
278 # Close streams
279 for stream in [modules_stream, classes_stream, properties_stream,
280 types_stream, constructors_stream] do
281
282 stream.close
283 var error = stream.last_error
284 if error != null then
285 toolcontext.error(null, "Error: failed to write Vim autocomplete file: {error}.")
286 end
287 end
288 end
289 end
290
291 redef class MModule
292 redef fun write_extra_doc(mainmodule, stream)
293 do
294 # Introduced classes
295 var class_intros = collect_intro_mclasses(protected_visibility).to_a
296 if class_intros.not_empty then
297 alpha_comparator.sort class_intros
298 stream.write line_separator*2
299 stream.write "## Introduced classes"
300
301 for c in class_intros do
302 stream.write line_separator
303 stream.write "* {c.name}"
304 var doc = c.intro.mdoc
305 if doc != null then stream.write ": {doc.content.first}"
306 end
307 end
308
309 # Introduced properties
310 var prop_intros = new Array[MPropDef]
311 for c in mclassdefs do
312 prop_intros.add_all c.collect_intro_mpropdefs(protected_visibility)
313 end
314
315 if prop_intros.not_empty then
316 alpha_comparator.sort prop_intros
317 stream.write line_separator*2
318 stream.write "## Introduced properties"
319 stream.write line_separator
320
321 for p in prop_intros do
322 p.mproperty.write_synopsis(mainmodule, stream)
323 end
324 end
325 end
326 end
327
328 redef class MProperty
329 private fun write_synopsis(mainmodule: MModule, stream: Writer)
330 do
331 if visibility == public_visibility then
332 stream.write "+ "
333 else stream.write "~ " # protected_visibility
334
335 if self isa MMethod then
336 if is_new and name != "new" then
337 stream.write "new "
338 else if is_init and name != "init" then
339 stream.write "init "
340 end
341 end
342
343 stream.write name
344
345 if self isa MMethod then
346 var intro = intro
347 assert intro isa MMethodDef
348 stream.write intro.msignature.to_s
349 end
350
351 var mdoc = intro.mdoc
352 if mdoc != null then
353 stream.write " # "
354 stream.write mdoc.content.first
355 end
356 stream.write line_separator
357 end
358 end