lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


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