1 " This file is part of NIT ( http://www.nitlanguage.org ).
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
7 " http://www.apache.org/licenses/LICENSE-2.0
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.
15 " Nit plugin for Vim, provides some advanced features
21 if exists("loaded_nit_plugin")
24 let loaded_nit_plugin = 1
26 " Scan all relevant Nit modules for the current directory to autocomplete
28 " The guard `g:nit_complete_done` ensures that its body is executed only
29 " once. The call to `nitls -M` analyzses the current directory. However,
30 " updating the module list can be forced using ForceNitComplete.
32 " To activate, add the following line to ~/.vimrc
34 " autocmd Filetype nit call NitComplete()
35 function NitComplete()
36 if !exists("g:nit_complete_done")
37 let g:nit_complete_done = 1
39 " Reset or backup the original complete
40 if !exists("g:nit_complete_backup")
41 let g:nit_complete_backup = &complete
43 silent let &complete = g:nit_complete_backup
47 " This gives us better results for Nit
49 set completeopt=longest,menuone,preview
51 " Do not predict small 3 letters keywords (or their prefix), they slow down
52 " prediction and some also require double-enter on end of line.
53 let g:acp_behaviorKeywordIgnores = ['new', 'var', 'in', 'do', 'els', 'end', 'ret', 'for', 'fun']
55 " Use nitls to compute all interesting files from the current directory and the standard library
56 for file in split(system('nitls -M standard .', '\n'))
57 silent let &complete = &complete . ',s' . file
61 " Compatibility with AutoComplPop
62 let g:acp_completeOption = &complete
63 let g:acp_ignorecaseOption = &ignorecase
65 " Redraw in case the user pressed some keys while waiting
70 " Force updating the Nit modules used for autocomplete
72 " It is recommended to manually call this function as needed. It can be mapped
75 " map <F2> :call ForceNitComplete()
77 " For small projects (or fast computers) you might want to call it on each
79 function ForceNitComplete()
80 unlet! g:nit_complete_done
84 " Get path to the best metadata file named `name`
86 " Returns an empty string if not found.
87 fun s:NitMetadataFile(name)
88 " Where are the generated metadata files?
89 if empty($NIT_VIM_DIR)
90 let metadata_dir = $HOME . '/.vim/nit'
92 let metadata_dir = $NIT_VIM_DIR
95 let path = metadata_dir . '/' . a:name
97 " Is there generated custom metadata files?
98 if ! filereadable(path)
99 let path = s:script_dir . '/' . a:name
101 " Is there standard metadata files?
102 if ! filereadable(path)
110 " Internal function to search for lines in `path` corresponding to the partial
111 " word `base`. Adds found and formated match to `matches`.
113 " Will order the results in 5 levels:
115 " 2. Common prefix matches
116 " 3. Substring matches
117 " 4. Synopsis matches
119 fun NitOmnifuncAddFromFile(base, matches, path)
120 let prefix_matches = []
121 let substring_matches = []
122 let synopsis_matches = []
125 let path = s:NitMetadataFile(a:path)
130 for line in readfile(path)
131 let words = split(line, '#====#', 1)
132 let name = get(words, 0, '')
137 call s:NitOmnifuncAddAMatch(a:matches, words, name)
138 elseif name =~? '^'.a:base
139 " Common-prefix match
140 call s:NitOmnifuncAddAMatch(prefix_matches, words, name)
141 elseif name =~? a:base
143 call s:NitOmnifuncAddAMatch(substring_matches, words, name)
144 elseif get(words, 2, '') =~? a:base
145 " Match in the synopsis
146 call s:NitOmnifuncAddAMatch(synopsis_matches, words, name)
147 elseif get(words, 3, '') =~? a:base
148 " Match in the longer doc
149 call s:NitOmnifuncAddAMatch(doc_matches, words, name)
153 " Assemble the final match list
154 call extend(a:matches, sort(prefix_matches))
155 call extend(a:matches, sort(substring_matches))
156 call extend(a:matches, sort(synopsis_matches))
157 call extend(a:matches, sort(doc_matches))
160 " Internal function to search parse the information from a metadata line
161 fun s:NitOmnifuncAddAMatch(matches, words, name)
162 let pretty = get(a:words, 1, '')
163 let synopsis = get(a:words, 2, '')
164 let desc = get(a:words, 3, '')
165 let desc = join(split(desc, '#nnnn#', 1), "\n")
166 if strlen(pretty) > 40
167 let pretty = pretty[:39] . '…'
169 call add(a:matches, {'word': a:name, 'abbr': pretty, 'menu': synopsis, 'info': desc, 'dup': 1})
172 " Omnifunc using metadata files generated by nitpick to offer better
173 " contextual autocomplete for Nit source code.
174 fun NitOmnifunc(findstart, base)
176 " locate the start of the word
177 let line = getline('.')
178 let start = col('.') - 1
179 while start > 0 && line[start - 1] =~ '\w'
184 " find keyword matching with "a:base"
187 " advanced suggestions
188 let cursor_line = getline('.')
190 " content of the line before the partial word
191 let line_prev_cursor = cursor_line[:col('.')-1]
193 let prev_char_at = strlen(line_prev_cursor) - 1
194 while prev_char_at > 0 && line_prev_cursor[prev_char_at] =~ '\s'
195 let prev_char_at -= 1
198 " Non whitespace char just before the partial word
199 let prev_char = line_prev_cursor[prev_char_at]
201 " Nity words on the current line before the partial word
202 let prev_words = split(line_prev_cursor, '\W\+')
204 " The word right before the partial word
205 let prev_word = get(prev_words, -1, '')
207 " Have we found a promising heuristic yet?
211 if prev_word == 'import' && ! (line_prev_cursor =~ 'fun')
213 call NitOmnifuncAddFromFile(a:base, matches, 'modules.txt')
216 " Classes (instanciable only)
217 if prev_word == 'new'
219 call NitOmnifuncAddFromFile(a:base, matches, 'constructors.txt')
222 " Other class references
223 if count(['class', 'super'], prev_word) > 0
225 call NitOmnifuncAddFromFile(a:base, matches, 'classes.txt')
229 if count(['class', 'super', 'nullable', 'isa', 'as'], prev_word) > 0 ||
230 \ line_prev_cursor =~ '[' || prev_char == ':' ||
231 \ (line_prev_cursor =~ 'fun' && line_prev_cursor =~ 'import' && (prev_word == 'import' || prev_char == ','))
233 call NitOmnifuncAddFromFile(a:base, matches, 'types.txt')
237 if prev_char == '.' || line_prev_cursor =~ '['
239 call NitOmnifuncAddFromFile(a:base, matches, 'properties.txt')
242 " If is nothing else...
244 " It may be a keyword
245 if strlen(a:base) > 0
246 for keyword in ['module', 'import', 'class', 'abstract', 'interface',
247 \'universal', 'enum', 'end', 'fun', 'type', 'init', 'redef', 'is',
248 \'do', 'var', 'extern', 'public', 'protected', 'private', 'intrude',
249 \'if', 'then', 'else', 'while', 'loop', 'for', 'in', 'and', 'or',
250 \'not', 'implies', 'return', 'continue', 'break', 'abort', 'assert',
251 \'new', 'isa', 'once', 'super', 'self', 'true', 'false', 'null',
252 \'as', 'nullable', 'isset', 'label']
254 if keyword =~ '^' . a:base
255 call add(matches, keyword)
260 " it may still be a method call or property access
261 call NitOmnifuncAddFromFile(a:base, matches, 'properties.txt')
264 return {'words': matches, 'refresh': 'always'}
268 " Show doc for the entity under the cursor in the preview window
270 if a:0 == 0 || empty(a:1)
272 let word = expand("<cword>")
274 let word = join(a:000, ' ')
277 " All possible docs (there may be more than one entity with the same name)
279 let prefix_matches = []
280 let substring_matches = []
281 let synopsis_matches = []
284 " Search in all metadata files
285 for file in ['modules', 'classes', 'properties']
286 let path = s:NitMetadataFile(file.'.txt')
291 for line in readfile(path)
292 let words = split(line, '#====#', 1)
293 let name = get(words, 0, '')
294 if name =~ '^'.word.'\>'
296 call s:NitdocAdd(docs, words)
297 elseif name =~? '^'.word
298 " Common-prefix match
299 call s:NitdocAdd(prefix_matches, words)
302 call s:NitdocAdd(substring_matches, words)
303 elseif get(words, 2, '') =~? word
304 " Match in the synopsis
305 call s:NitdocAdd(synopsis_matches, words)
306 elseif get(words, 3, '') =~? word
307 " Match in the longer doc
308 call s:NitdocAdd(doc_matches, words)
313 " Unite all results in prefered order
314 call extend(docs, sort(prefix_matches))
315 call extend(docs, sort(substring_matches))
316 call extend(docs, sort(synopsis_matches))
317 call extend(docs, sort(doc_matches))
319 " Found no doc, give up
320 if empty(docs) || !(join(docs, '') =~ '\w')
321 echo 'Nitdoc found nothing for "' . word . '"'
325 " Open the preview window on a temp file
326 execute "silent pedit " . tempname()
328 " Change to preview window
331 " Show all found doc one after another
339 delete " the first empty line
342 setlocal buftype=nofile
345 setlocal bufhidden=delete
347 " Change back to the source buffer
352 " Internal function to search parse the information from a metadata line
353 fun s:NitdocAdd(matches, words)
354 let desc = get(a:words, 3, '')
355 let desc = join(split(desc, '#nnnn#', 1), "\n")
356 call add(a:matches, desc)
359 " Call `git grep` on the word under the cursor
361 " Shows declarations first, then all matches, in the preview window.
363 let word = expand("<cword>")
365 execute 'silent !(git grep "\\(module\\|class\\|universal\\|interface\\|var\\|fun\\) '.word.'";'.
366 \'echo; git grep '.word.') > '.out
368 " Open the preview window on a temp file
369 execute "silent pedit " . out
371 " Change to preview window
375 setlocal buftype=nofile
378 setlocal bufhidden=delete
380 " Change back to the source buffer
385 " Activate the omnifunc on Nit files
386 autocmd FileType nit set omnifunc=NitOmnifunc
388 " Define the user command Nitdoc for ease of use
389 command -nargs=* Nitdoc call Nitdoc("<args>")
391 let s:script_dir = fnamemodify(resolve(expand('<sfile>:p')), ':h')