[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: autocompletion code
- From: Dimitris Papavasiliou <jimmyp@...>
- Date: Tue, 27 Apr 2004 01:47:02 +0300
wow! quite a lot of feedback. I'll try to aswer to all replies here.
First of all let me say that I wrote all this yesterday somewhere
between 2 and 4 am so it wasn't anything finsihed, I just wanted to see
if I could get it to work at all, so I didn't do much (any) error
checking etc. After trying in vain to sleep for half an hour or so I got
up again and by 7am I was (almost) finished.
Second, this code is supposed to be used in a line-by-line interpreter
(a game console actually) so a) speed is of practically no importance
b) I can see of no reason why running table indexing code could hurt in
this context. Granted, arbitrary metamethods could be called but they
would be called anyway when the user hits the return key so if anything
bad happens it's his/her fault. If I'm missing something please do
enlighten me.
scott wrote:
One thing your code does not consider that can be very helpfull is the use
of the ':' seperator in addition to '.' I wrote something very similar for
the console of our game and I don't remember there being any trick to
getting ':' working. Just check for ':' anywhere you would also look for
'.'
Yes, although I don't use this seperator (haven't had to until now) I
did add that to the 7am version. Well I just had to add a ':' next to
the '%.' in the patterns. I agree that it isn't really important wether
completions make any sense syntactically (e.g. only complete functions
after the : seperator) it's just supposed to be an aid the user (not
only for typing but also as a reminder of the various vars/keys in case
he can't remember the exact name). Code to handle this could be added
though.
What I more interested in is how easy it would be to support completion
of all table indexing syntax (foo.bar, foo:bar, foo["bar"], foo[value])
and also to allow the table to be any expression that could result in a
table (if it doesn loadstring will take care of it). This isn't very
important in my case from a practical point of view, most of the time
I'll only need what I have until now but it would be nice if it would be
easy, for the sake of completeness and consistency (I'd much rather
write in the manual that tab autocompletes any table indexing
expressions that that it only works for the table.key syntax where table
can only be a string of the form t1.t2. ... .tn). I doubt it would be
easy to do this though especially with the backward scanning while
statement I'm using since you'd have to find a substring with the
longest expression that can result in a value (that is compile and run).
Anyway if anyone wants to tackle I'd be interested in the results.
philipe wrote:
**
-- scan the string backwards starting at the cursor to find the substring
-- we're interested in (sub)
--[[ Why are you including '\' in the accepted chars? ]]
while string.find(string.sub(s, i, i), "[%w_\.]") do
--[[ Little optimisation on this kind of things: put found chars in an array, and
table.concat them ]]
sub = string.sub(s, i, i) .. (sub or "")
i = i - 1
end
the slash in there was supposed to escape the following dot. I initially
thought that '\' was the escaping character and lost a good quarter
wondering why it didn't work. I forgot to change that to '%'. BTW thanks
for the dummy '_' variable convention.
Sam wrote:
I tried your piece of code, and I found that your autocompletion forgets an element
when you specify the begining of the string. This element is the string itself.
No, and the comment on the last loop makes this clear: there is no point in listing
the string itself, as there is nothing more to autocomplete...
Actually if you use the 'enders' mentioned by rlake in his post this
would make sense, you would complete the ender. Also, now that I think
of it, it should be included anyway as all possible candidates should be
listed.
rlake wrote:
So here is one option:
---
local pathStart = string.find(s, "[%w_.]*$") -- could be [%w_.:]
local prefixStart = string.find(s, "[%w_]*$", pathStart)
local tab
if pathStart == prefixStart then
tab = _G
else
tab = assert(loadstring("return " .. string.sub(s, pathStart,
prefixStart - 2)))() or {} -- but see note **
end
local prefix = string.sub(s, prefixStart)
---
Since you want to complete at the cursor's position which isn't
necessarily the end of the string you'd first have to get the substring
that ends at that position. But otherwise this is very interesting. I'm
especially interested in wether the patterns could be tweaked to allow
any expression that could evaluate to a table, but I'm way too tired to
think about this right now.
There are a few problems, still. The obvious one is that this will only
work with global variables; locals will not be autocompleted. (And use of
locals really ought to be encouraged.) Beyond that, there is the problem
that somewhere along the path there might be a table with an __index
metamethod, hence arbitrary code might be run at the lines marked **. (In
the second option, this could be avoided by replacing that line with
"local nextTab = rawget(tab, w)" but avoiding metamethods might also
produce misleading results.
Most of this is not very important in my case. Since you only get one
line to type into you can't make much use of locals and as I said
metamethods would be run anyway on command execution. The worst part is
that if a table has 'keys' implemented as metamethods they won't show up
as completion candidates, but that's the way lua works I suppose and
there's not much that can be done about this (or is there?)
I am assuming that this whole thing will run inside a protected call, but
even so in the first version there is no guarantee that the object
returned at the line marked "**" is actually a table, or even that running
the code will not result in an error; if it turns out to be a userdata
with a table interface, the attempt to iterate over its keys might fail in
unpredictable ways (or do unpredictable things), and so on. In the second
version, the explicit test will prevent autocomplete from working in the
case of userdatas, but it quite possibly wouldn't work anyway.
As I have said my code was a simple draft with no error checking. My new
code does all this.
However, I think that is not correct: if the current string is a valid
key, that should be included, with the addition of an appropriate
character (space if it is a scalar, "(" if it is a function, "." if it is
a table.) So that could be done, too:
-- table with a default value
local enders = setmetatable({["function"] = "(", table = "."}, {__index =
function() return " " end})
-- this time we won't worry about the character because of the explicit
-- test
local pat = "^" .. prefix
for k, v in pairs(tab) do
if k == prefix then print(k .. enders[type(v)])
elseif string.find(k, pat) then print(k)
end
end
I thought of implementing something like this, thanks for the code.
The latest version of my code follows in case anyone is interested in
it. Thanks to everyone for all the help and ideas.
s = "print(string"
local substring, match = "", ""
local matches = {}
local k, p, t = string.len(s)
local prefix, path
-- I know it gets very inefficient at points like this but in the case of a
-- command line completer speed is of hardly any importance
while string.find(string.sub(s, k, k), "[%w_%.]") do
substring = string.sub(s, k, k) .. substring
k = k - 1
end
-- I found the pattern I was looking for! Also using pcall the parentheses
-- around path are necessay to make sure that and empty prefix string won't
-- compile to "return " which will run with no problems and will end up in
-- p beeing true and t beeing nil intead of _G
_, _, path, prefix = string.find(substring, "^([%w_%.]-)%.?([%w_]*)$")
p, t = pcall(loadstring("return (" .. path .. ")"))
if p == false then
t = getfenv()
end
if type(t) == "table" then
for key in pairs(t) do
if type(key) == "string" and
string.find(key, "^" .. prefix) then
table.insert(matches, key)
end
end
end
if table.getn(matches) == 1 then
suffix = string.sub(matches[1], string.len(prefix) + 1)
print("to be completed suffix: " .. suffix)
else
table.sort(matches)
for i, match in pairs(matches) do
print(match)
end
end