Skip to content

Commit 6ee6a69

Browse files
committed
Support for omni completion by searching package.{path,cpath}
1 parent 9d68e7b commit 6ee6a69

File tree

6 files changed

+240
-16
lines changed

6 files changed

+240
-16
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ The [Lua][lua] file type plug-in for [Vim][vim] makes it easier to work with Lua
1212

1313
* The ['completefunc'][cfu] option is set to allow completion of Lua 5.1 keywords, global variables and library members using Control-X Control-U
1414

15+
* 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!
16+
1517
* Several [text-objects][tob] are defined so you can jump between blocks and functions
1618

1719
* 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).
6466

6567
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).
6668

69+
### The `lua_complete_omni` option
70+
71+
This option is disabled by default for two reasons:
72+
73+
* The omni completion support works by enumerating and loading all installed modules. **If module loading has side effects this can have unintended consequences!**
74+
* 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).
75+
76+
If you want to use the omni completion despite the warnings above, execute the following command:
77+
78+
:let g:lua_complete_omni = 1
79+
80+
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 :-)
81+
6782
## Contact
6883

6984
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
85100
[req]: http://www.lua.org/manual/5.1/manual.html#pdf-require
86101
[lrv]: http://www.vim.org/scripts/script.php?script_id=1291
87102
[cfu]: http://vimdoc.sourceforge.net/htmldoc/options.html#%27completefunc%27
103+
[ofu]: http://vimdoc.sourceforge.net/htmldoc/options.html#%27omnifunc%27
88104
[tob]: http://vimdoc.sourceforge.net/htmldoc/motion.html#text-objects
89105
[mit]: http://vimdoc.sourceforge.net/htmldoc/usr_05.html#matchit-install
90106
[zip]: http://peterodding.com/code/vim/downloads/lua-ftplugin.zip

autoload/xolox/lua.vim

Lines changed: 127 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ function! xolox#lua#includeexpr(fname) " {{{1
3131
endfunction
3232

3333
function! xolox#lua#getsearchpath() " {{{1
34+
" TODO Support for Lua Interface for Vim.
35+
" TODO Support for $LUA_CPATH, "package.cpath".
3436
let lua_path = xolox#lua#getopt('lua_path', '')
3537
if empty(lua_path)
3638
let lua_path = $LUA_PATH
@@ -240,22 +242,25 @@ endif
240242

241243
function! xolox#lua#completefunc(init, base) " {{{1
242244
if a:init
243-
let prefix = strpart(getline('.'), 0, col('.') - 2)
244-
return match(prefix, '\w\+\.\?\w*$')
245-
else
246-
let items = []
247-
if xolox#lua#getopt('lua_complete_keywords', 1)
248-
call extend(items, s:keywords)
249-
endif
250-
if xolox#lua#getopt('lua_complete_globals', 1)
251-
call extend(items, s:globals)
252-
endif
253-
if xolox#lua#getopt('lua_complete_library', 1)
254-
call extend(items, s:library)
255-
endif
256-
let regex = string('\V' . escape(a:base, '\'))
257-
return filter(items, 'v:val.word =~ ' . regex)
245+
return s:get_completion_prefix()
246+
endif
247+
let items = []
248+
if xolox#lua#getopt('lua_complete_keywords', 1)
249+
call extend(items, s:keywords)
258250
endif
251+
if xolox#lua#getopt('lua_complete_globals', 1)
252+
call extend(items, s:globals)
253+
endif
254+
if xolox#lua#getopt('lua_complete_library', 1)
255+
call extend(items, s:library)
256+
endif
257+
let pattern = xolox#misc#escape#pattern(a:base)
258+
return filter(items, 'v:val.word =~ pattern')
259+
endfunction
260+
261+
function! s:get_completion_prefix()
262+
let prefix = strpart(getline('.'), 0, col('.') - 2)
263+
return match(prefix, '\w\+\.\?\w*$')
259264
endfunction
260265

261266
function! xolox#lua#completedynamic() " {{{1
@@ -274,6 +279,113 @@ function! xolox#lua#completedynamic() " {{{1
274279
return '.'
275280
endfunction
276281

282+
function! xolox#lua#omnifunc(init, base) " {{{1
283+
if a:init
284+
return s:get_completion_prefix()
285+
elseif !xolox#lua#getopt('lua_complete_omni', 0)
286+
throw printf("%s: omni completion needs to be explicitly enabled, see the readme!", s:script)
287+
endif
288+
if !exists('s:omnifunc_candidates')
289+
let s:omnifunc_candidates = xolox#lua#getomnicandidates()
290+
endif
291+
if a:base == ''
292+
return s:omnifunc_candidates
293+
else
294+
let pattern = xolox#misc#escape#pattern(a:base)
295+
return filter(copy(s:omnifunc_candidates), 'v:val =~ pattern')
296+
endif
297+
endfunction
298+
299+
function! xolox#lua#getomnicandidates() " {{{1
300+
let starttime = xolox#misc#timer#start()
301+
let modules = {}
302+
for searchpath in s:getsearchpaths()
303+
call s:expandsearchpath(searchpath, modules)
304+
endfor
305+
let output = xolox#lua#dofile(s:omnicomplete_script, keys(modules))
306+
let lines = split(output, "\n")
307+
call sort(lines, 1)
308+
call xolox#misc#timer#stop("%s: Collected omni completion candidates in %s", s:script, starttime)
309+
return lines
310+
endfunction
311+
312+
let s:omnicomplete_script = expand('<sfile>:p:h:h:h') . '/misc/lua-ftplugin/omnicomplete.lua'
313+
314+
function! s:getsearchpaths()
315+
" TODO Replace with xolox#lua#getsearchpath()
316+
" Get the values of "package.path" and "package.cpath".
317+
let output = system('lua -e "io.write(package.path, ''\n'', package.cpath)"')
318+
let lines = split(output, "\n")
319+
if v:shell_error || len(lines) != 2
320+
call xolox#misc#msg#warn("%s: Failed to determine Lua's search paths!", s:script)
321+
return []
322+
endif
323+
return lines
324+
endfunction
325+
326+
function! s:expandsearchpath(searchpath, modules)
327+
" Collect the names of all installed modules by traversing the search paths.
328+
for template in split(a:searchpath, ';')
329+
let components = split(template, '?')
330+
if len(components) != 2
331+
let msg = "%s: Failed to parse search path entry: %s"
332+
call xolox#misc#msg#debug(msg, s:script, template)
333+
continue
334+
endif
335+
let [prefix, suffix] = components
336+
" XXX Never recursively search current working directory because
337+
" it might be arbitrarily deep, e.g. when working directory is /
338+
if prefix =~ '^.[\\/]$'
339+
let msg = "%s: Refusing to expand dangerous search path entry: %s"
340+
call xolox#misc#msg#debug(msg, s:script, template)
341+
continue
342+
endif
343+
let pattern = substitute(template, '?', '**/*', 'g')
344+
call xolox#misc#msg#debug("%s: Transformed %s -> %s", s:script, template, pattern)
345+
let msg = "%s: Failed to convert pathname to module name, %s doesn't match! (%s: '%s', pathname: '%s')"
346+
for pathname in split(glob(pattern), "\n")
347+
if pathname[0 : len(prefix)-1] != prefix
348+
" Validate prefix of resulting pathname.
349+
call xolox#misc#msg#warn(msg, s:script, 'prefix', 'prefix', prefix, pathname)
350+
elseif pathname[-len(suffix) : -1] != suffix
351+
" Validate suffix of resulting pathname.
352+
call xolox#misc#msg#warn(msg, s:script, 'suffix', 'suffix', suffix, pathname)
353+
elseif pathname !~ 'test'
354+
let relative = pathname[len(prefix) : -len(suffix)-1]
355+
let modulename = substitute(relative, '[\\/]\+', '.', 'g')
356+
let a:modules[modulename] = 1
357+
call xolox#misc#msg#debug("%s: Transformed '%s' -> '%s'", s:script, pathname, modulename)
358+
endif
359+
endfor
360+
endfor
361+
endfunction
362+
363+
function! xolox#lua#dofile(pathname, arguments) " {{{1
364+
" First try to use the Lua Interface for Vim.
365+
try
366+
call xolox#misc#msg#debug("%s: Trying Lua Interface for Vim ..", s:script)
367+
redir => output
368+
lua arg = vim.eval('a:arguments')
369+
execute 'silent luafile' fnameescape(a:pathname)
370+
redir END
371+
if !empty(output)
372+
return output
373+
endif
374+
catch
375+
redir END
376+
call xolox#misc#msg#warn("%s: %s (at %s)", s:script, v:exception, v:throwpoint)
377+
endtry
378+
" Fall back to the command line Lua interpreter.
379+
call xolox#misc#msg#debug("Falling back to external Lua interpreter ..")
380+
let output = system(join(['lua', a:pathname] + a:arguments))
381+
if v:shell_error
382+
let msg = "%s: Failed to retrieve omni completion candidates (output: '%s')"
383+
call xolox#misc#msg#warn(msg, s:script, output)
384+
return ''
385+
else
386+
return output
387+
endfunction
388+
277389
" }}}
278390

279391
" Enable line continuation.

doc/lua.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ code in Vim by providing the following features:
2020
- The |'completefunc'| option is set to allow completion of Lua 5.1 keywords,
2121
global variables and library members using Control-X Control-U
2222

23+
- The |'omnifunc'| option is set to allow dynamic completion of all modules
24+
installed on the system using Control-X Control-O, however it needs to be
25+
explicitly enabled by setting the |lua_complete_omni| option because this
26+
functionality may have undesired side effects!
27+
2328
- Several |text-objects| are defined so you can jump between blocks and
2429
functions
2530

@@ -109,6 +114,28 @@ When you type a dot after a word the Lua file type plug-in will automatically
109114
start completion. To disable this behavior you can set this option to false
110115
(0).
111116

117+
-------------------------------------------------------------------------------
118+
The *lua_complete_omni* option
119+
120+
This option is disabled by default for two reasons:
121+
122+
- The omni completion support works by enumerating and loading all installed
123+
modules. If module loading has side effects this can have unintended
124+
consequences!
125+
126+
- Because all modules installed on the system are loaded, collecting the
127+
completion candidates can be slow. After the first run the completion
128+
candidates are cached so this will only bother you once (until you restart
129+
Vim).
130+
131+
If you want to use the omni completion despite the warnings above, execute the
132+
following command:
133+
>
134+
:let g:lua_complete_omni = 1
135+
136+
Now when you type Control-X Control-O Vim will hang for a moment, after which
137+
you should be presented with an enormous list of completion candidates :-)
138+
112139
===============================================================================
113140
*lua-contact*
114141
Contact ~

ftplugin/lua.vim

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
" Author: Peter Odding <peter@peterodding.com>
44
" Last Change: June 14, 2011
55
" URL: http://peterodding.com/code/vim/lua-ftplugin
6-
" Version: 0.5.7
6+
" Version: 0.6
77

88
" Support for automatic update using the GLVS plug-in.
99
" GetLatestVimScripts: 3625 1 :AutoInstall: lua.zip
@@ -31,6 +31,10 @@ call add(s:undo_ftplugin, 'setlocal inc< inex<')
3131
setlocal completefunc=xolox#lua#completefunc
3232
call add(s:undo_ftplugin, 'setlocal completefunc<')
3333

34+
" Enable dynamic completion by searching "package.path" and "package.cpath". {{{1
35+
setlocal omnifunc=xolox#lua#omnifunc
36+
call add(s:undo_ftplugin, 'setlocal omnifunc<')
37+
3438
" Set a filename filter for the Windows file open/save dialogs. {{{1
3539
if has('gui_win32') && !exists('b:browsefilter')
3640
let b:browsefilter = "Lua Files (*.lua)\t*.lua\nAll Files (*.*)\t*.*\n"

misc/lua-ftplugin/complete.lua

100644100755
File mode changed.

misc/lua-ftplugin/omnicomplete.lua

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#!/usr/bin/env lua
2+
3+
--[[
4+
5+
Author: Peter Odding <peter@peterodding.com>
6+
Last Change: June 14, 2011
7+
URL: http://peterodding.com/code/vim/lua-ftplugin
8+
9+
This Lua script is executed by the Lua file type plug-in for Vim to provide
10+
dynamic completion of function names defined by installed Lua modules. This
11+
works by expanding package.path and package.cpath in Vim script, loading every
12+
module found on the search path into this Lua script and then dumping the
13+
global state.
14+
15+
]]
16+
17+
local keywords = { ['and'] = true, ['break'] = true, ['do'] = true,
18+
['else'] = true, ['elseif'] = true, ['end'] = true, ['false'] = true,
19+
['for'] = true, ['function'] = true, ['if'] = true, ['in'] = true,
20+
['local'] = true, ['nil'] = true, ['not'] = true, ['or'] = true,
21+
['repeat'] = true, ['return'] = true, ['then'] = true, ['true'] = true,
22+
['until'] = true, ['while'] = true }
23+
24+
local function isident(s)
25+
return type(s) == 'string' and s:find('^[A-Za-z_][A-Za-z_0-9]*$') and not keywords[s]
26+
end
27+
28+
local function dump(table, path, cache)
29+
local printed = false
30+
for key, value in pairs(table) do
31+
if isident(key) then
32+
local path = path and (path .. '.' .. key) or key
33+
local vtype = type(value)
34+
if vtype == 'function' then
35+
printed = true
36+
print(path .. "()")
37+
elseif vtype ~= 'table' then
38+
printed = true
39+
print(path)
40+
else
41+
if vtype == 'table' and not cache[value] then
42+
cache[value] = true
43+
if dump(value, path, cache) then
44+
printed = true
45+
else
46+
print(path .. "[]")
47+
end
48+
end
49+
end
50+
end
51+
end
52+
return printed
53+
end
54+
55+
-- Load installed modules.
56+
-- XXX What if module loading has side effects? It shouldn't, but still...
57+
for _, modulename in ipairs(arg) do
58+
pcall(require, modulename)
59+
end
60+
61+
-- Generate completion candidates from global state.
62+
local cache = {}
63+
cache[_G] = true
64+
cache[package.loaded] = true
65+
dump(_G, nil, cache)

0 commit comments

Comments
 (0)