Skip to content

Commit

Permalink
Support for omni completion by searching package.{path,cpath}
Browse files Browse the repository at this point in the history
  • Loading branch information
xolox committed Jun 14, 2011
1 parent 9d68e7b commit 6ee6a69
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 16 deletions.
16 changes: 16 additions & 0 deletions README.md
Expand Up @@ -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`)
Expand Down Expand Up @@ -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].
Expand All @@ -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
Expand Down
142 changes: 127 additions & 15 deletions autoload/xolox/lua.vim
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down
27 changes: 27 additions & 0 deletions doc/lua.txt
Expand Up @@ -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

Expand Down Expand Up @@ -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 ~
Expand Down
6 changes: 5 additions & 1 deletion ftplugin/lua.vim
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down
Empty file modified misc/lua-ftplugin/complete.lua 100644 → 100755
Empty file.
65 changes: 65 additions & 0 deletions misc/lua-ftplugin/omnicomplete.lua
@@ -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.