Progress I made so far:
In
Sprung's examples a widget waits for a specific text command to run a specific code. I wanted a solution to run an arbitrary text command. So I found the loadstring() function which makes an executable command out of string (and this is when I found that Spring uses Lua 5.1, because it is load() in newer versions, and there are some other changes in Lua, and I need to switch from 4th to 2nd edition of PiL).
To filter the text commands I start them with 'sudo' (can be any string), like "/luaui sudo Spring.GetOrderToUnit(..)" and the widget filters it (see code below).
The problem with loadstring() is that the resulting command runs in global environment and does not have the access to all the local variables and functions of the widget. So, for example, if I want to use in my command UnitID of a selected unit, I have two options:
1. To make the widget print the UnitID and then to manually insert it into the command. The code then will be
function widget:TextCommand(str)
if string.find(str, "^sudo ") then -- if a string starts with "sudo "
local code = string.sub( str, 5 ) -- get the rest of the string after "sudo "
Echo( "Executing: " .. code )
cmd = loadstring( code ) -- prepare a command but do not execute it yet
local ok,msg = pcall( cmd ) -- run the command with "protected call" so in a case of error the widget will not be removed
if not ok then Echo( "Error: " .. msg ) end -- in case of an error, print the error message
end
end
2. To prepare an environment for the cmd which will include the local stuff. This is the trickiest part, I tried to automate it with loops of debug.getlocal and debug.getupvalue, traversing along the stack levels, but couldn't find where the widget's locals are residing. So I came with a dirty solution to manually add the locals of interest to a special table and then to make the new environment out of it, adding the index of globals. The code is then:
function widget:TextCommand(str)
if string.find(str, "^sudo ") then -- if a string starts with "sudo "
local code = string.sub( str, 5 ) -- get the rest of the string after "sudo "
Echo( "Executing: " .. code )
cmd = loadstring( code ) -- prepare a command but do not execute it yet
local env = getfenv(2) -- get the environment of the caller function (a widget handler?)
local locals = { z = z, selectedUnits = selectedUnits } -- put here all the local variables you want access to
setfenv(cmd, setmetatable(locals, { __index = env._G })) -- set environment for the command, including both the locals and the global index
local ok,msg = pcall( cmd ) -- run the command with "protected call" so in a case of error the widget will not be removed
if not ok then Echo( "Error: " .. msg ) end -- in case of an error, print the error message
end
end
With the latter, I could run things like
/luaui sudo Spring.Echo( z )
and
/luaui sudo Spring.Echo( Spring.GetUnitMaxRange( selectedUnits[1] ))
and to get in the console
Executing: Spring.Echo( Spring.GetUnitMaxRange( selectedUnits[1] ))
600.000061
Executing: Spring.Echo( z )
ZZZ
and if the command fails, I get its error message, but the widget is not getting removed.
TODO/questions:
1. Anyone can explain how to make automatically a table of all the locals of a widget from inside a callin?
2. I'd like to make a dedicated widget serving the interactive commands, so that in a widget I'm working on, I don't need to include the snippet above (may be just 'require'?). Probably need to replace "sudo" with a widget's name so the dedicated widget will know in which widget to run the command.
3. I'm still interested to know where can I find the full list of callins.