X-Git-Url: http://nitlanguage.org diff --git a/misc/vim/plugin/nit.vim b/misc/vim/plugin/nit.vim index ea308a5..86d5e45 100644 --- a/misc/vim/plugin/nit.vim +++ b/misc/vim/plugin/nit.vim @@ -46,7 +46,7 @@ function NitComplete() " This gives us better results for Nit set noignorecase - set completeopt=longest,menuone + set completeopt=longest,menuone,preview " Do not predict small 3 letters keywords (or their prefix), they slow down " prediction and some also require double-enter on end of line. @@ -80,3 +80,312 @@ function ForceNitComplete() unlet! g:nit_complete_done call NitComplete() endfunction + +" Get path to the best metadata file named `name` +" +" Returns an empty string if not found. +fun s: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 5 levels: +" 1. Exact matches +" 2. Common prefix matches +" 3. Substring matches +" 4. Synopsis matches +" 5. Doc matches +fun NitOmnifuncAddFromFile(base, matches, path) + let prefix_matches = [] + let substring_matches = [] + let synopsis_matches = [] + let doc_matches = [] + + let path = s: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 s:NitOmnifuncAddAMatch(a:matches, words, name) + elseif name =~? '^'.a:base + " Common-prefix match + call s:NitOmnifuncAddAMatch(prefix_matches, words, name) + elseif name =~? a:base + " Substring match + call s:NitOmnifuncAddAMatch(substring_matches, words, name) + elseif get(words, 2, '') =~? a:base + " Match in the synopsis + call s:NitOmnifuncAddAMatch(synopsis_matches, words, name) + elseif get(words, 3, '') =~? a:base + " Match in the longer doc + call s:NitOmnifuncAddAMatch(doc_matches, words, name) + endif + endfor + + " Assemble the final match list + call extend(a:matches, sort(prefix_matches)) + call extend(a:matches, sort(substring_matches)) + call extend(a:matches, sort(synopsis_matches)) + call extend(a:matches, sort(doc_matches)) +endfun + +" Internal function to search parse the information from a metadata line +fun s: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(...) + if a:0 == 0 || empty(a:1) + " Word under cursor + let word = expand("") + else + let word = join(a:000, ' ') + endif + + " All possible docs (there may be more than one entity with the same name) + let docs = [] + let prefix_matches = [] + let substring_matches = [] + let synopsis_matches = [] + let doc_matches = [] + + " Search in all metadata files + for file in ['modules', 'classes', 'properties'] + let path = s: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.'\>' + " Exact match + call s:NitdocAdd(docs, words) + elseif name =~? '^'.word + " Common-prefix match + call s:NitdocAdd(prefix_matches, words) + elseif name =~? word + " Substring match + call s:NitdocAdd(substring_matches, words) + elseif get(words, 2, '') =~? word + " Match in the synopsis + call s:NitdocAdd(synopsis_matches, words) + elseif get(words, 3, '') =~? word + " Match in the longer doc + call s:NitdocAdd(doc_matches, words) + endif + endfor + endfor + + " Unite all results in prefered order + call extend(docs, sort(prefix_matches)) + call extend(docs, sort(substring_matches)) + call extend(docs, sort(synopsis_matches)) + call extend(docs, sort(doc_matches)) + + " Found no doc, give up + if empty(docs) || !(join(docs, '') =~ '\w') + echo 'Nitdoc found nothing for "' . word . '"' + 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 + execute 0 + delete " the first empty line + + " Set options + setlocal buftype=nofile + setlocal noswapfile + setlocal syntax=none + setlocal bufhidden=delete + + " Change back to the source buffer + wincmd p + redraw! +endfun + +" Internal function to search parse the information from a metadata line +fun s:NitdocAdd(matches, words) + let desc = get(a:words, 3, '') + let desc = join(split(desc, '#nnnn#', 1), "\n") + call add(a:matches, desc) +endfun + +" Call `git grep` on the word under the cursor +" +" Shows declarations first, then all matches, in the preview window. +fun NitGitGrep() + let word = expand("") + let out = tempname() + execute 'silent !(git grep "\\(module\\|class\\|universal\\|interface\\|var\\|fun\\) '.word.'";'. + \'echo; git grep '.word.') > '.out + + " Open the preview window on a temp file + execute "silent pedit " . out + + " Change to preview window + wincmd P + + " 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 + +" Define the user command Nitdoc for ease of use +command -nargs=* Nitdoc call Nitdoc("") + +let s:script_dir = fnamemodify(resolve(expand(':p')), ':h')