[prev in list] [next in list] [prev in thread] [next in thread] 

List:       lua-l
Subject:    Re: autocompletion code
From:       Dimitris Papavasiliou <jimmyp () hal ! csd ! auth ! gr>
Date:       2004-04-26 22:47:02
Message-ID: 408D9166.3070409 () hal ! csd ! auth ! gr
[Download RAW message or body]

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

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic