Makefile: talk about nit_env.sh after successful `make all`
[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 end
49 end
50
51 redef class MEntity
52 private fun field_separator: String do return "#====#"
53 private fun line_separator: String do return "#nnnn#"
54
55 private fun write_doc(mainmodule: MModule, stream: Writer)
56 do
57 # 1. Short name for autocompletion
58 stream.write complete_name
59 stream.write field_separator
60
61 # 2. Full signature
62 stream.write complete_name
63 write_signature_to_stream(stream)
64 stream.write field_separator
65
66 # 3. Doc synopsis
67 var mdoc = complete_mdoc
68 if mdoc != null then
69 stream.write mdoc.content.first
70 end
71
72 # 4. Full doc with extra
73 stream.write field_separator
74 stream.write "# "
75 stream.write full_name
76 write_signature_to_stream(stream)
77 if mdoc != null then
78 for i in 2.times do stream.write line_separator
79 stream.write mdoc.content.join(line_separator)
80 end
81 write_extra_doc(mainmodule, stream)
82
83 stream.write "\n"
84 end
85
86 private fun write_signature_to_stream(stream: Writer) do end
87
88 # Actual name used in completion
89 private fun complete_name: String do return name
90
91 # Doc to use in completion
92 private fun complete_mdoc: nullable MDoc do return mdoc
93
94 # Extra auto documentation to append to the `stream`
95 private fun write_extra_doc(mainmodule: MModule, stream: Writer) do end
96 end
97
98 redef class MMethodDef
99 redef fun write_signature_to_stream(stream)
100 do
101 var msignature = msignature
102 if msignature != null then
103 stream.write msignature.to_s
104 end
105 end
106 end
107
108 redef class MAttributeDef
109 redef fun write_signature_to_stream(stream)
110 do
111 var static_mtype = static_mtype
112 if static_mtype != null then
113 stream.write stream.to_s
114 end
115 end
116 end
117
118 # Use `MClassDef` as anchor for its constructors only
119 redef class MClassDef
120 private var target_constructor: nullable MMethodDef = null
121
122 redef fun complete_name
123 do
124 var target_constructor = target_constructor
125 assert target_constructor != null
126
127 var params
128 var mparameters = mclass.mparameters
129 if not mparameters.is_empty then
130 params = "[{mparameters.join(", ")}]"
131 else
132 params = ""
133 end
134
135 if target_constructor.name != "init" and target_constructor.name != "new" then
136 return name + params + "." + target_constructor.name
137 end
138
139 return name + params
140 end
141
142 redef fun complete_mdoc
143 do
144 var target_constructor = target_constructor
145 assert target_constructor != null
146
147 if target_constructor.name != "init" and target_constructor.name != "new" then
148 return target_constructor.mdoc
149 end
150
151 return mdoc
152 end
153 end
154
155 redef class MClassType
156 redef fun write_extra_doc(mainmodule, stream)
157 do
158 # Super classes
159 stream.write line_separator*2
160 stream.write "## Class hierarchy"
161
162 var direct_supers = [for s in mclass.in_hierarchy(mainmodule).direct_greaters do s.name]
163 if not direct_supers.is_empty then
164 alpha_comparator.sort direct_supers
165 stream.write line_separator
166 stream.write "* Direct super classes: "
167 stream.write direct_supers.join(", ")
168 end
169
170 var supers = [for s in mclass.in_hierarchy(mainmodule).greaters do s.name]
171 supers.remove mclass.name
172 if not supers.is_empty then
173 alpha_comparator.sort supers
174 stream.write line_separator
175 stream.write "* All super classes: "
176 stream.write supers.join(", ")
177 end
178
179 var direct_subs = [for s in mclass.in_hierarchy(mainmodule).direct_smallers do s.name]
180 if not direct_subs.is_empty then
181 alpha_comparator.sort direct_subs
182 stream.write line_separator
183 stream.write "* Direct sub classes: "
184 stream.write direct_subs.join(", ")
185 end
186
187 var subs = [for s in mclass.in_hierarchy(mainmodule).smallers do s.name]
188 subs.remove mclass.name
189 if not subs.is_empty then
190 alpha_comparator.sort subs
191 stream.write line_separator
192 stream.write "* All sub classes: "
193 stream.write subs.join(", ")
194 end
195
196 # List other properties
197 stream.write line_separator*2
198 stream.write "## Properties"
199 stream.write line_separator
200 var props = mclass.collect_accessible_mproperties(protected_visibility).to_a
201 alpha_comparator.sort props
202 for prop in props do
203 if mclass.name == "Object" or prop.intro.mclassdef.mclass.name != "Object" then
204 prop.write_synopsis(mainmodule, stream)
205 end
206 end
207 end
208
209 redef fun complete_mdoc do return mclass.intro.mdoc
210 end
211
212 private class AutocompletePhase
213 super Phase
214
215 redef fun process_mainmodule(mainmodule, given_mmodules)
216 do
217 if not toolcontext.opt_vim_autocomplete.value then return
218
219 var compile_dir = "NIT_VIM_DIR".environ
220 if compile_dir.is_empty then compile_dir = "HOME".environ / ".vim/nit"
221 compile_dir.mkdir
222
223 var modules_stream = new FileWriter.open(compile_dir / "modules.txt")
224 var classes_stream = new FileWriter.open(compile_dir / "classes.txt")
225 var constructors_stream = new FileWriter.open(compile_dir / "constructors.txt")
226 var types_stream = new FileWriter.open(compile_dir / "types.txt")
227 var properties_stream = new FileWriter.open(compile_dir / "properties.txt")
228
229 # Got all known modules
230 var model = mainmodule.model
231 for mmodule in model.mmodules do
232 mmodule.write_doc(mainmodule, modules_stream)
233 end
234
235 # TODO list other modules from the Nit lib
236
237 # Get all known classes
238 for mclass in model.mclasses do
239 if not mainmodule.is_visible(mclass.intro_mmodule, public_visibility) then continue
240 var mclass_intro = mclass.intro
241
242 # Can it be instantiated?
243 if mclass.kind != interface_kind and mclass.kind != abstract_kind then
244
245 for prop in mclass.collect_accessible_mproperties(public_visibility) do
246 if prop isa MMethod and prop.is_init then
247 mclass_intro.target_constructor = prop.intro
248 mclass_intro.write_doc(mainmodule, constructors_stream)
249 end
250 end
251 mclass_intro.target_constructor = null
252 end
253
254 # Always add to types and classes
255 mclass.mclass_type.write_doc(mainmodule, classes_stream)
256 mclass.mclass_type.write_doc(mainmodule, types_stream)
257 end
258
259 # Get all known properties
260 for mproperty in model.mproperties do
261 var intro_mmodule = mproperty.intro_mclassdef.mmodule
262 if not mainmodule.is_visible(intro_mmodule, public_visibility) then continue
263
264 # Is it a virtual type?
265 if mproperty isa MVirtualTypeProp then
266 mproperty.intro.write_doc(mainmodule, types_stream)
267 continue
268 end
269
270 # Skip properties beginning with @ or _
271 var first_letter = mproperty.name.chars.first
272 if first_letter == '@' or first_letter == '_' then continue
273
274 mproperty.intro.write_doc(mainmodule, properties_stream)
275 end
276
277 # Close streams
278 for stream in [modules_stream, classes_stream, properties_stream,
279 types_stream, constructors_stream] do
280
281 stream.close
282 var error = stream.last_error
283 if error != null then
284 toolcontext.error(null, "Error: failed to write Vim autocomplete file: {error}.")
285 end
286 end
287 end
288 end
289
290 redef class MModule
291 redef fun write_extra_doc(mainmodule, stream)
292 do
293 # Introduced classes
294 var class_intros = collect_intro_mclasses(protected_visibility).to_a
295 if class_intros.not_empty then
296 alpha_comparator.sort class_intros
297 stream.write line_separator*2
298 stream.write "## Introduced classes"
299
300 for c in class_intros do
301 stream.write line_separator
302 stream.write "* {c.name}"
303 var doc = c.intro.mdoc
304 if doc != null then stream.write ": {doc.content.first}"
305 end
306 end
307
308 # Introduced properties
309 var prop_intros = new Array[MPropDef]
310 for c in mclassdefs do
311 prop_intros.add_all c.collect_intro_mpropdefs(protected_visibility)
312 end
313
314 if prop_intros.not_empty then
315 alpha_comparator.sort prop_intros
316 stream.write line_separator*2
317 stream.write "## Introduced properties"
318 stream.write line_separator
319
320 for p in prop_intros do
321 p.mproperty.write_synopsis(mainmodule, stream)
322 end
323 end
324 end
325 end
326
327 redef class MProperty
328 private fun write_synopsis(mainmodule: MModule, stream: Writer)
329 do
330 if visibility == public_visibility then
331 stream.write "+ "
332 else stream.write "~ " # protected_visibility
333
334 if self isa MMethod then
335 if is_new and name != "new" then
336 stream.write "new "
337 else if is_init and name != "init" then
338 stream.write "init "
339 end
340 end
341
342 stream.write name
343
344 if self isa MMethod then
345 var intro = intro
346 assert intro isa MMethodDef
347 stream.write intro.msignature.to_s
348 end
349
350 var mdoc = intro.mdoc
351 if mdoc != null then
352 stream.write " # "
353 stream.write mdoc.content.first
354 end
355 stream.write line_separator
356 end
357 end