+
+" Get path to the best metadata file named `name`
+"
+" Returns an empty string if not found.
+fun NitMetadataFile(name)
+ " Where are the generated metadata files?
+ if empty($NIT_VIM_DIR)
+ let metadata_dir = $HOME . '/.vim/nit'
+ else
+ let metadata_dir = $NIT_VIM_DIR
+ end
+
+ let path = metadata_dir . '/' . a:name
+
+ " Is there generated custom metadata files?
+ if ! filereadable(path)
+ let path = s:script_dir . '/' . a:name
+
+ " Is there standard metadata files?
+ if ! filereadable(path)
+ return ''
+ endif
+ endif
+
+ return path
+endfun
+
+" Internal function to search for lines in `path` corresponding to the partial
+" word `base`. Adds found and formated match to `matches`.
+"
+" Will order the results in 3 levels:
+" 1. Exact matches
+" 2. Common prefix matches
+" 3. Substring matches
+fun NitOmnifuncAddFromFile(base, matches, path)
+ let prefix_matches = []
+ let substring_matches = []
+
+ let path = NitMetadataFile(a:path)
+ if empty(path)
+ return
+ endif
+
+ for line in readfile(path)
+ let words = split(line, '#====#', 1)
+ let name = get(words, 0, '')
+
+ " Add?
+ if name == a:base
+ " Exact match
+ call NitOmnifuncAddAMatch(a:matches, words, name)
+ elseif name =~ '^'.a:base
+ " Common-prefix match
+ call NitOmnifuncAddAMatch(prefix_matches, words, name)
+ elseif name =~ a:base
+ " Substring match
+ call NitOmnifuncAddAMatch(substring_matches, words, name)
+ endif
+ endfor
+
+ " Assemble the final match list
+ call extend(a:matches, sort(prefix_matches))
+ call extend(a:matches, sort(substring_matches))
+endfun
+
+" Internal function to search parse the information from a metadata line
+fun NitOmnifuncAddAMatch(matches, words, name)
+ let pretty = get(a:words, 1, '')
+ let synopsis = get(a:words, 2, '')
+ let desc = get(a:words, 3, '')
+ let desc = join(split(desc, '#nnnn#', 1), "\n")
+ if strlen(pretty) > 40
+ let pretty = pretty[:39] . '…'
+ endif
+ call add(a:matches, {'word': a:name, 'abbr': pretty, 'menu': synopsis, 'info': desc, 'dup': 1})
+endfun
+
+" Omnifunc using metadata files generated by nitpick to offer better
+" contextual autocomplete for Nit source code.
+fun NitOmnifunc(findstart, base)
+ if a:findstart
+ " locate the start of the word
+ let line = getline('.')
+ let start = col('.') - 1
+ while start > 0 && line[start - 1] =~ '\w'
+ let start -= 1
+ endwhile
+ return start
+ else
+ " find keyword matching with "a:base"
+ let matches = []
+
+ " advanced suggestions
+ let cursor_line = getline('.')
+
+ " content of the line before the partial word
+ let line_prev_cursor = cursor_line[:col('.')-1]
+
+ let prev_char_at = strlen(line_prev_cursor) - 1
+ while prev_char_at > 0 && line_prev_cursor[prev_char_at] =~ '\s'
+ let prev_char_at -= 1
+ endwhile
+
+ " Non whitespace char just before the partial word
+ let prev_char = line_prev_cursor[prev_char_at]
+
+ " Nity words on the current line before the partial word
+ let prev_words = split(line_prev_cursor, '\W\+')
+
+ " The word right before the partial word
+ let prev_word = get(prev_words, -1, '')
+
+ " Have we found a promising heuristic yet?
+ let handled = 0
+
+ " Modules
+ if prev_word == 'import' && ! (line_prev_cursor =~ 'fun')
+ let handled = 1
+ call NitOmnifuncAddFromFile(a:base, matches, 'modules.txt')
+ endif
+
+ " Classes (instanciable only)
+ if prev_word == 'new'
+ let handled = 1
+ call NitOmnifuncAddFromFile(a:base, matches, 'constructors.txt')
+ endif
+
+ " Other class references
+ if count(['class', 'super'], prev_word) > 0
+ let handled = 1
+ call NitOmnifuncAddFromFile(a:base, matches, 'classes.txt')
+ endif
+
+ " Types
+ if count(['class', 'super', 'nullable', 'isa', 'as'], prev_word) > 0 ||
+ \ line_prev_cursor =~ '[' || prev_char == ':' ||
+ \ (line_prev_cursor =~ 'fun' && line_prev_cursor =~ 'import' && (prev_word == 'import' || prev_char == ','))
+ let handled = 1
+ call NitOmnifuncAddFromFile(a:base, matches, 'types.txt')
+ endif
+
+ " Properties
+ if prev_char == '.' || line_prev_cursor =~ '['
+ let handled = 1
+ call NitOmnifuncAddFromFile(a:base, matches, 'properties.txt')
+ endif
+
+ " If is nothing else...
+ if !handled
+ " It may be a keyword
+ if strlen(a:base) > 0
+ for keyword in ['module', 'import', 'class', 'abstract', 'interface',
+ \'universal', 'enum', 'end', 'fun', 'type', 'init', 'redef', 'is',
+ \'do', 'var', 'extern', 'public', 'protected', 'private', 'intrude',
+ \'if', 'then', 'else', 'while', 'loop', 'for', 'in', 'and', 'or',
+ \'not', 'implies', 'return', 'continue', 'break', 'abort', 'assert',
+ \'new', 'isa', 'once', 'super', 'self', 'true', 'false', 'null',
+ \'as', 'nullable', 'isset', 'label']
+
+ if keyword =~ '^' . a:base
+ call add(matches, keyword)
+ endif
+ endfor
+ endif
+
+ " it may still be a method call or property access
+ call NitOmnifuncAddFromFile(a:base, matches, 'properties.txt')
+ endif
+
+ return {'words': matches, 'refresh': 'always'}
+ endif
+endfun
+
+" Show doc for the entity under the cursor in the preview window
+fun Nitdoc()
+ " Word under cursor
+ let word = expand("<cword>")
+
+ " All possible docs (there may be more than one entity with the same name)
+ let docs = []
+
+ " Search in all metadata files
+ for file in ['modules', 'classes', 'properties']
+ let path = NitMetadataFile(file.'.txt')
+ if empty(path)
+ continue
+ endif
+
+ for line in readfile(path)
+ let words = split(line, '#====#', 1)
+ let name = get(words, 0, '')
+ if name =~ '^' . word
+ " It fits our word, get long doc
+ let desc = get(words,3,'')
+ let desc = join(split(desc, '#nnnn#', 1), "\n")
+ call add(docs, desc)
+ endif
+ endfor
+ endfor
+
+ " Found no doc, give up
+ if empty(docs) || !(join(docs, '') =~ '\w')
+ return
+ endif
+
+ " Open the preview window on a temp file
+ execute "silent pedit " . tempname()
+
+ " Change to preview window
+ wincmd P
+
+ " Show all found doc one after another
+ for doc in docs
+ if doc =~ '\w'
+ silent put = doc
+ silent put = ''
+ endif
+ endfor
+
+ " Set options
+ setlocal buftype=nofile
+ setlocal noswapfile
+ setlocal syntax=none
+ setlocal bufhidden=delete
+
+ " Change back to the source buffer
+ wincmd p
+ redraw!
+endfun
+
+" Activate the omnifunc on Nit files
+autocmd FileType nit set omnifunc=NitOmnifunc
+
+let s:script_dir = fnamemodify(resolve(expand('<sfile>:p')), ':h')