Permalink
Browse files

Support for omni completion by searching package.{path,cpath}

  • Loading branch information...
1 parent 9d68e7b commit 6ee6a69e52baf4bf34176390197353e04e388539 @xolox committed Jun 14, 2011
Showing with 240 additions and 16 deletions.
  1. +16 −0 README.md
  2. +127 −15 autoload/xolox/lua.vim
  3. +27 −0 doc/lua.txt
  4. +5 −1 ftplugin/lua.vim
  5. 0 misc/lua-ftplugin/complete.lua
  6. +65 −0 misc/lua-ftplugin/omnicomplete.lua
View
@@ -12,6 +12,8 @@ The [Lua][lua] file type plug-in for [Vim][vim] makes it easier to work with Lua
* The ['completefunc'][cfu] option is set to allow completion of Lua 5.1 keywords, global variables and library members using Control-X Control-U
+ * The ['omnifunc'][ofu] option is set to allow dynamic completion of all modules installed on the system using Control-X Control-O, however it needs to be explicitly enabled by setting the `lua_complete_omni` option because this functionality may have undesired side effects!
+
* Several [text-objects][tob] are defined so you can jump between blocks and functions
* A pretty nifty hack of the [matchit plug-in][mit] is included: When the cursor is on a `function` or `return` keyword the `%` mapping cycles between the relevant keywords (`function`, `return`, `end`), this also works for branching statements (`if`, `elseif`, `else`, `end`) and looping statements (`for`, `while`, `repeat`, `until`, `end`)
@@ -64,6 +66,19 @@ To disable completion of library functions you can set this option to false (0).
When you type a dot after a word the Lua file type plug-in will automatically start completion. To disable this behavior you can set this option to false (0).
+### The `lua_complete_omni` option
+
+This option is disabled by default for two reasons:
+
+ * The omni completion support works by enumerating and loading all installed modules. **If module loading has side effects this can have unintended consequences!**
+ * Because all modules installed on the system are loaded, collecting the completion candidates can be slow. After the first run the completion candidates are cached so this will only bother you once (until you restart Vim).
+
+If you want to use the omni completion despite the warnings above, execute the following command:
+
+ :let g:lua_complete_omni = 1
+
+Now when you type Control-X Control-O Vim will hang for a moment, after which you should be presented with an enormous list of completion candidates :-)
+
## Contact
If you have questions, bug reports, suggestions, etc. the author can be contacted at <peter@peterodding.com>. The latest version is available at <http://peterodding.com/code/vim/lua-ftplugin> and <http://github.com/xolox/vim-lua-ftplugin>. If you like this plug-in please vote for it on [Vim Online][script].
@@ -85,6 +100,7 @@ This software is licensed under the [MIT license](http://en.wikipedia.org/wiki/M
[req]: http://www.lua.org/manual/5.1/manual.html#pdf-require
[lrv]: http://www.vim.org/scripts/script.php?script_id=1291
[cfu]: http://vimdoc.sourceforge.net/htmldoc/options.html#%27completefunc%27
+[ofu]: http://vimdoc.sourceforge.net/htmldoc/options.html#%27omnifunc%27
[tob]: http://vimdoc.sourceforge.net/htmldoc/motion.html#text-objects
[mit]: http://vimdoc.sourceforge.net/htmldoc/usr_05.html#matchit-install
[zip]: http://peterodding.com/code/vim/downloads/lua-ftplugin.zip
View
@@ -31,6 +31,8 @@ function! xolox#lua#includeexpr(fname) " {{{1
endfunction
function! xolox#lua#getsearchpath() " {{{1
+ " TODO Support for Lua Interface for Vim.
+ " TODO Support for $LUA_CPATH, "package.cpath".
let lua_path = xolox#lua#getopt('lua_path', '')
if empty(lua_path)
let lua_path = $LUA_PATH
@@ -240,22 +242,25 @@ endif
function! xolox#lua#completefunc(init, base) " {{{1
if a:init
- let prefix = strpart(getline('.'), 0, col('.') - 2)
- return match(prefix, '\w\+\.\?\w*$')
- else
- let items = []
- if xolox#lua#getopt('lua_complete_keywords', 1)
- call extend(items, s:keywords)
- endif
- if xolox#lua#getopt('lua_complete_globals', 1)
- call extend(items, s:globals)
- endif
- if xolox#lua#getopt('lua_complete_library', 1)
- call extend(items, s:library)
- endif
- let regex = string('\V' . escape(a:base, '\'))
- return filter(items, 'v:val.word =~ ' . regex)
+ return s:get_completion_prefix()
+ endif
+ let items = []
+ if xolox#lua#getopt('lua_complete_keywords', 1)
+ call extend(items, s:keywords)
endif
+ if xolox#lua#getopt('lua_complete_globals', 1)
+ call extend(items, s:globals)
+ endif
+ if xolox#lua#getopt('lua_complete_library', 1)
+ call extend(items, s:library)
+ endif
+ let pattern = xolox#misc#escape#pattern(a:base)
+ return filter(items, 'v:val.word =~ pattern')
+endfunction
+
+function! s:get_completion_prefix()
+ let prefix = strpart(getline('.'), 0, col('.') - 2)
+ return match(prefix, '\w\+\.\?\w*$')
endfunction
function! xolox#lua#completedynamic() " {{{1
@@ -274,6 +279,113 @@ function! xolox#lua#completedynamic() " {{{1
return '.'
endfunction
+function! xolox#lua#omnifunc(init, base) " {{{1
+ if a:init
+ return s:get_completion_prefix()
+ elseif !xolox#lua#getopt('lua_complete_omni', 0)
+ throw printf("%s: omni completion needs to be explicitly enabled, see the readme!", s:script)
+ endif
+ if !exists('s:omnifunc_candidates')
+ let s:omnifunc_candidates = xolox#lua#getomnicandidates()
+ endif
+ if a:base == ''
+ return s:omnifunc_candidates
+ else
+ let pattern = xolox#misc#escape#pattern(a:base)
+ return filter(copy(s:omnifunc_candidates), 'v:val =~ pattern')
+ endif
+endfunction
+
+function! xolox#lua#getomnicandidates() " {{{1
+ let starttime = xolox#misc#timer#start()
+ let modules = {}
+ for searchpath in s:getsearchpaths()
+ call s:expandsearchpath(searchpath, modules)
+ endfor
+ let output = xolox#lua#dofile(s:omnicomplete_script, keys(modules))
+ let lines = split(output, "\n")
+ call sort(lines, 1)
+ call xolox#misc#timer#stop("%s: Collected omni completion candidates in %s", s:script, starttime)
+ return lines
+endfunction
+
+let s:omnicomplete_script = expand('<sfile>:p:h:h:h') . '/misc/lua-ftplugin/omnicomplete.lua'
+
+function! s:getsearchpaths()
+ " TODO Replace with xolox#lua#getsearchpath()
+ " Get the values of "package.path" and "package.cpath".
+ let output = system('lua -e "io.write(package.path, ''\n'', package.cpath)"')
+ let lines = split(output, "\n")
+ if v:shell_error || len(lines) != 2
+ call xolox#misc#msg#warn("%s: Failed to determine Lua's search paths!", s:script)
+ return []
+ endif
+ return lines
+endfunction
+
+function! s:expandsearchpath(searchpath, modules)
+ " Collect the names of all installed modules by traversing the search paths.
+ for template in split(a:searchpath, ';')
+ let components = split(template, '?')
+ if len(components) != 2
+ let msg = "%s: Failed to parse search path entry: %s"
+ call xolox#misc#msg#debug(msg, s:script, template)
+ continue
+ endif
+ let [prefix, suffix] = components
+ " XXX Never recursively search current working directory because
+ " it might be arbitrarily deep, e.g. when working directory is /
+ if prefix =~ '^.[\\/]$'
+ let msg = "%s: Refusing to expand dangerous search path entry: %s"
+ call xolox#misc#msg#debug(msg, s:script, template)
+ continue
+ endif
+ let pattern = substitute(template, '?', '**/*', 'g')
+ call xolox#misc#msg#debug("%s: Transformed %s -> %s", s:script, template, pattern)
+ let msg = "%s: Failed to convert pathname to module name, %s doesn't match! (%s: '%s', pathname: '%s')"
+ for pathname in split(glob(pattern), "\n")
+ if pathname[0 : len(prefix)-1] != prefix
+ " Validate prefix of resulting pathname.
+ call xolox#misc#msg#warn(msg, s:script, 'prefix', 'prefix', prefix, pathname)
+ elseif pathname[-len(suffix) : -1] != suffix
+ " Validate suffix of resulting pathname.
+ call xolox#misc#msg#warn(msg, s:script, 'suffix', 'suffix', suffix, pathname)
+ elseif pathname !~ 'test'
+ let relative = pathname[len(prefix) : -len(suffix)-1]
+ let modulename = substitute(relative, '[\\/]\+', '.', 'g')
+ let a:modules[modulename] = 1
+ call xolox#misc#msg#debug("%s: Transformed '%s' -> '%s'", s:script, pathname, modulename)
+ endif
+ endfor
+ endfor
+endfunction
+
+function! xolox#lua#dofile(pathname, arguments) " {{{1
+ " First try to use the Lua Interface for Vim.
+ try
+ call xolox#misc#msg#debug("%s: Trying Lua Interface for Vim ..", s:script)
+ redir => output
+ lua arg = vim.eval('a:arguments')
+ execute 'silent luafile' fnameescape(a:pathname)
+ redir END
+ if !empty(output)
+ return output
+ endif
+ catch
+ redir END
+ call xolox#misc#msg#warn("%s: %s (at %s)", s:script, v:exception, v:throwpoint)
+ endtry
+ " Fall back to the command line Lua interpreter.
+ call xolox#misc#msg#debug("Falling back to external Lua interpreter ..")
+ let output = system(join(['lua', a:pathname] + a:arguments))
+ if v:shell_error
+ let msg = "%s: Failed to retrieve omni completion candidates (output: '%s')"
+ call xolox#misc#msg#warn(msg, s:script, output)
+ return ''
+ else
+ return output
+endfunction
+
" }}}
" Enable line continuation.
View
@@ -20,6 +20,11 @@ code in Vim by providing the following features:
- The |'completefunc'| option is set to allow completion of Lua 5.1 keywords,
global variables and library members using Control-X Control-U
+ - The |'omnifunc'| option is set to allow dynamic completion of all modules
+ installed on the system using Control-X Control-O, however it needs to be
+ explicitly enabled by setting the |lua_complete_omni| option because this
+ functionality may have undesired side effects!
+
- Several |text-objects| are defined so you can jump between blocks and
functions
@@ -109,6 +114,28 @@ When you type a dot after a word the Lua file type plug-in will automatically
start completion. To disable this behavior you can set this option to false
(0).
+-------------------------------------------------------------------------------
+The *lua_complete_omni* option
+
+This option is disabled by default for two reasons:
+
+ - The omni completion support works by enumerating and loading all installed
+ modules. If module loading has side effects this can have unintended
+ consequences!
+
+ - Because all modules installed on the system are loaded, collecting the
+ completion candidates can be slow. After the first run the completion
+ candidates are cached so this will only bother you once (until you restart
+ Vim).
+
+If you want to use the omni completion despite the warnings above, execute the
+following command:
+>
+ :let g:lua_complete_omni = 1
+
+Now when you type Control-X Control-O Vim will hang for a moment, after which
+you should be presented with an enormous list of completion candidates :-)
+
===============================================================================
*lua-contact*
Contact ~
View
@@ -3,7 +3,7 @@
" Author: Peter Odding <peter@peterodding.com>
" Last Change: June 14, 2011
" URL: http://peterodding.com/code/vim/lua-ftplugin
-" Version: 0.5.7
+" Version: 0.6
" Support for automatic update using the GLVS plug-in.
" GetLatestVimScripts: 3625 1 :AutoInstall: lua.zip
@@ -31,6 +31,10 @@ call add(s:undo_ftplugin, 'setlocal inc< inex<')
setlocal completefunc=xolox#lua#completefunc
call add(s:undo_ftplugin, 'setlocal completefunc<')
+" Enable dynamic completion by searching "package.path" and "package.cpath". {{{1
+setlocal omnifunc=xolox#lua#omnifunc
+call add(s:undo_ftplugin, 'setlocal omnifunc<')
+
" Set a filename filter for the Windows file open/save dialogs. {{{1
if has('gui_win32') && !exists('b:browsefilter')
let b:browsefilter = "Lua Files (*.lua)\t*.lua\nAll Files (*.*)\t*.*\n"
View
0 misc/lua-ftplugin/complete.lua 100644 → 100755
No changes.
@@ -0,0 +1,65 @@
+#!/usr/bin/env lua
+
+--[[
+
+Author: Peter Odding <peter@peterodding.com>
+Last Change: June 14, 2011
+URL: http://peterodding.com/code/vim/lua-ftplugin
+
+This Lua script is executed by the Lua file type plug-in for Vim to provide
+dynamic completion of function names defined by installed Lua modules. This
+works by expanding package.path and package.cpath in Vim script, loading every
+module found on the search path into this Lua script and then dumping the
+global state.
+
+]]
+
+local keywords = { ['and'] = true, ['break'] = true, ['do'] = true,
+ ['else'] = true, ['elseif'] = true, ['end'] = true, ['false'] = true,
+ ['for'] = true, ['function'] = true, ['if'] = true, ['in'] = true,
+ ['local'] = true, ['nil'] = true, ['not'] = true, ['or'] = true,
+ ['repeat'] = true, ['return'] = true, ['then'] = true, ['true'] = true,
+ ['until'] = true, ['while'] = true }
+
+local function isident(s)
+ return type(s) == 'string' and s:find('^[A-Za-z_][A-Za-z_0-9]*$') and not keywords[s]
+end
+
+local function dump(table, path, cache)
+ local printed = false
+ for key, value in pairs(table) do
+ if isident(key) then
+ local path = path and (path .. '.' .. key) or key
+ local vtype = type(value)
+ if vtype == 'function' then
+ printed = true
+ print(path .. "()")
+ elseif vtype ~= 'table' then
+ printed = true
+ print(path)
+ else
+ if vtype == 'table' and not cache[value] then
+ cache[value] = true
+ if dump(value, path, cache) then
+ printed = true
+ else
+ print(path .. "[]")
+ end
+ end
+ end
+ end
+ end
+ return printed
+end
+
+-- Load installed modules.
+-- XXX What if module loading has side effects? It shouldn't, but still...
+for _, modulename in ipairs(arg) do
+ pcall(require, modulename)
+end
+
+-- Generate completion candidates from global state.
+local cache = {}
+cache[_G] = true
+cache[package.loaded] = true
+dump(_G, nil, cache)

0 comments on commit 6ee6a69

Please sign in to comment.