misc/vim: use the metadata files for a better autocompletion
authorAlexis Laferrière <alexis.laf@xymus.net>
Wed, 4 Feb 2015 17:01:57 +0000 (12:01 -0500)
committerAlexis Laferrière <alexis.laf@xymus.net>
Fri, 6 Feb 2015 18:46:32 +0000 (13:46 -0500)
Signed-off-by: Alexis Laferrière <alexis.laf@xymus.net>

misc/vim/plugin/nit.vim

index ea308a5..d58df14 100644 (file)
@@ -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,155 @@ function ForceNitComplete()
        unlet! g:nit_complete_done
        call NitComplete()
 endfunction
+
+" 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 = []
+
+       " 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:path
+       " Is there generated custom metadata files?
+       if ! filereadable(path)
+               let path = s:script_dir . '/' . a:path
+
+               " Is there standard metadata files?
+               if ! filereadable(path)
+                       return
+               endif
+       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[:37] . '...'
+       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
+               if count(['new', 'super', 'class', 'nullable'], 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, 'classes.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
+
+" Activate the omnifunc on Nit files
+autocmd FileType nit set omnifunc=NitOmnifunc
+
+let s:script_dir = fnamemodify(resolve(expand('<sfile>:p')), ':h')