forked from facebookarchive/iTorch
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcompleter.lua
234 lines (215 loc) · 8.33 KB
/
completer.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
--[[
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
]]--
--- Completion engine
-- Compute possible matches for input text.
-- Based on [lua-rlcompleter](https://github.com/rrthomas/lua-rlcompleter) by
-- Patrick Rapin and Reuben Thomas.
-- @alias M
local lfs = pcall(require, "lfs") and require"lfs"
local cowrap, coyield = coroutine.wrap, coroutine.yield
local M = { }
--- The list of Lua keywords
M.keywords = {
'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for',
'function', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat',
'return', 'then', 'true', 'until', 'while'
}
--- Callback function to set final character.
-- Called to set the final chracter if a single match occur. Typically, it is used
-- to close a string if completion occurs inside an open string. It is intended
-- to be redefined as default function does nothing.
-- @param[type=string] char Either an empty string or a string of length 1.
M.final_char_setter = function(char) end
--- References all completion generators (completers).
-- Completion is context sensitive and there is 2 *contexts* : values
-- (@{completers.value}) and strings (@{completers.string}). Some arguments are
-- given to the completer the first time it's called.
--
-- Each context has its corresponding table (sequence) with *completers* inside.
-- These tables works a bit like `package.loaders` : each loader is called as a
-- coroutine and can generate possible matches (by yielding strings). But, unlike
-- `package.loaders`, all completers are always called; even if matches has been
-- generated by previous ones.
--
-- It it not the completer job to filter matches with current text (this is done
-- by the complettion engine), but just generate all possible matches.
M.completers = { }
--- Completers for Lua values.
-- Completers are called with two arguments :
--
-- * `value` to complete (not necessarily a table)
-- * `separator` used to index value (`.`, `:` or `[`)
--
-- Default completers for values are :
--
-- 1. Table field completer: searches for fields inside `value` if it's a table.
-- 2. Metatable completer: if `value` has a metatable, calls first completer
-- with that table.
--
-- @table completers.value
M.completers.value = { }
--- Completers for strings.
-- Completers are called with the string to complete.
-- If `lfs` is can be loaded, a completer for files and folders is provided, it
-- search for items starting with given string in current directory.
-- @table completers.string
M.completers.string = { }
-- Table fields completer
table.insert(M.completers.value, function(t, sep)
if type(t) ~= "table" then return end
for k, v in pairs(t) do
if type(k) == "number" and sep == "[" then
coyield(k.."]")
elseif type(k) == "string" and (sep ~= ":" or type(v) == "function") then
coyield(k)
end
end
end)
-- Metamethod completer
table.insert(M.completers.value, function(t, sep)
local mt = getmetatable(t)
if mt and type(mt.__index) == "table" then
return M.completers.value[1](mt.__index, sep) -- use regular table completer on metatable
end
end)
-- tensor/storage/torch-classes completer
table.insert(M.completers.value, function(t, sep)
local function enumerate_metatable(typename)
if typename == nil then return end
local metatable = torch.getmetatable(typename)
for k, v in pairs(metatable) do
if type(k) == "number" and sep == "[" then
coyield(k.."]")
elseif type(k) == "string" and (sep ~= ":" or type(v) == "function") then
coyield(k)
end
end
if torch.typename(metatable) ~= typename then
enumerate_metatable(torch.typename(metatable))
end
end
enumerate_metatable(torch.typename(t))
end)
-- This function does the same job as the default completion of readline,
-- completing paths and filenames. Rewritten because
-- rl_basic_word_break_characters is different.
-- Uses LuaFileSystem (lfs) module for this task (if present).
if lfs then
table.insert(M.completers.string, function(str)
local path, name = str:match("(.*)[\\/]+(.*)")
path = (path or ".") .. "/"
name = name or str
-- avoid to trigger an error if folder does not exists
if not lfs.attributes(path) then return end
for f in lfs.dir(path) do
if (lfs.attributes(path .. f) or {}).mode == 'directory' then
coyield(f .. "/")
else
coyield(f)
end
end
end)
end
-- This function is called back by C function do_completion, itself called
-- back by readline library, in order to complete the current input line.
function M.complete(word, line, startpos, endpos)
-- Helper function registering possible completion words, verifying matches.
local matches = {}
local function add(value)
value = tostring(value)
if value:match("^" .. word) then
matches[#matches + 1] = value
end
end
local function call_completors(completers, ...)
for _, completer in ipairs(completers) do
local coro = cowrap(completer)
local match = coro(...) -- first call => give parameters
if match then
add(match)
-- continue calling to get next matches
for match in coro do add(match) end
end
end
end
-- This function is called in a context where a keyword or a global
-- variable can be inserted. Local variables cannot be listed!
local function add_globals()
for _, k in ipairs(M.keywords) do
add(k)
end
call_completors(M.completers.value, _G)
end
-- Main completion function. It evaluates the current sub-expression
-- to determine its type. Currently supports tables fields, global
-- variables and function prototype completion.
local function contextual_list(expr, sep, str)
if str then
M.final_char_setter('"')
return call_completors(M.completers.string, str)
end
M.final_char_setter("")
if expr and expr ~= "" then
local v = loadstring("return " .. expr)
if v then
call_completors(M.completers.value, v(), sep)
end
end
if #matches == 0 then
add_globals()
end
end
-- This complex function tries to simplify the input line, by removing
-- literal strings, full table constructors and balanced groups of
-- parentheses. Returns the sub-expression preceding the word, the
-- separator item ( '.', ':', '[', '(' ) and the current string in case
-- of an unfinished string literal.
local function simplify_expression(expr)
-- Replace annoying sequences \' and \" inside literal strings
expr = expr:gsub("\\(['\"])", function (c)
return string.format("\\%03d", string.byte(c))
end)
local curstring
-- Remove (finished and unfinished) literal strings
while true do
local idx1, _, equals = expr:find("%[(=*)%[")
local idx2, _, sign = expr:find("(['\"])")
if idx1 == nil and idx2 == nil then
break
end
local idx, startpat, endpat
if (idx1 or math.huge) < (idx2 or math.huge) then
idx, startpat, endpat = idx1, "%[" .. equals .. "%[", "%]" .. equals .. "%]"
else
idx, startpat, endpat = idx2, sign, sign
end
if expr:sub(idx):find("^" .. startpat .. ".-" .. endpat) then
expr = expr:gsub(startpat .. "(.-)" .. endpat, " STRING ")
else
expr = expr:gsub(startpat .. "(.*)", function (str)
curstring = str
return "(CURSTRING "
end)
end
end
expr = expr:gsub("%b()"," PAREN ") -- Remove groups of parentheses
expr = expr:gsub("%b{}"," TABLE ") -- Remove table constructors
-- Avoid two consecutive words without operator
expr = expr:gsub("(%w)%s+(%w)","%1|%2")
expr = expr:gsub("%s+", "") -- Remove now useless spaces
-- This main regular expression looks for table indexes and function calls.
return curstring, expr:match("([%.%w%[%]_]-)([:%.%[%(])" .. word .. "$")
end
-- Now call the processing functions and return the list of results.
local str, expr, sep = simplify_expression(line:sub(1, endpos))
contextual_list(expr, sep, str)
return matches
end
return M