Guest User

Lua Explorer „Advanced, fix 4

a guest
Apr 18th, 2016
125
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 15.19 KB | None | 0 0
  1. -- Lua Explorer „Advanced“,
  2. -- based on Lua Explorer by EGez
  3. -- modded by John Doe: http://forum.farmanager.com/viewtopic.php?f=60&t=7988
  4. -- release 2
  5.  
  6. --[[
  7.     Lua Explorer
  8.  
  9.     Explore Lua environment in your Far manager
  10.  
  11.     Author: Eugen Gez (EGez/http://forum.farmanager.com)
  12.     updates, suggestions, etc.:
  13.     http://forum.farmanager.com/viewtopic.php?f=15&t=7521
  14.  
  15.  
  16.     BE CAREFUL:
  17.         calling some functions could cause deadlocks!!!
  18.         you will need to kill far process in such cases
  19.  
  20.     do not call functions like debug.debug() or io.* functions
  21.     that read from stdin unless you know what you are doing
  22.  
  23.  
  24.     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
  25.     ANY USE IS AT YOUR OWN RISK.
  26.  
  27.     Do what you want with this code, but please do not remove this comment
  28.     and write your changes down.
  29.  
  30.     <obsolete changelog removed> --JD
  31.  
  32.     UPDATED:
  33.  
  34.     Change "msgbox" to "far.Message". Big thanks John Doe and Acerbic for idea! /* VictorVG */
  35.  
  36. ]]
  37.  
  38. -- assert(Far, 'This is a LuaMacro for Far manager')
  39.  
  40. local uuid  = win.Uuid('7646f761-8954-42ca-9cfc-e3f98a1c54d3')
  41. local help  = [[
  42. There are some keys available:
  43.  
  44. F1               Show this help
  45. F4               Edit selected object
  46. Del              Delete selected object
  47. Ins              Add an object to current table
  48. Ctrl+M           Show metatable
  49. Ctrl+F           Show/hide functions
  50. Ctrl+T           Toggle sort by type
  51.  
  52. for functions:
  53.  
  54. Enter            Call function (params prompted)
  55. F3               Show some function info
  56. Shift+F3         Show some function info (LuaJIT required)
  57. Alt+F4           Open function definition (if available) in editor
  58. Ctrl+Up          Show upvalues (editable)
  59. Ctrl+Down        Show environment (editable)
  60.  
  61.  
  62. Copy to clipboard:
  63.  
  64. Ctrl+Ins         value
  65. Ctrl+Shift+Ins   key
  66. ]]
  67.  
  68. local omit = {}
  69. local brkeys = {}
  70. local tables_first = true
  71.  
  72. -- format values for menu items and message boxes
  73. local function valfmt(val, mode)
  74.     local t = type(val)
  75.     if t == 'string' then
  76.         if not pcall(win.Utf8ToUtf16, val) then val = '<illegal value>' end
  77.         return (mode=='edit' and ('%q'):format(val) or
  78.                (mode~='list' or val=='' or val:match' $') and '"' ..  val .. '"' or
  79.                 val), t
  80.     elseif t == 'number' then
  81.         return (mode=="edit" and '0x%x --[[ %s ]]' or '0x%08x (%s)'):format(val, val), t
  82.     end
  83.     return tostring(val), t
  84. end
  85.  
  86. -- make menu item for far.Menu(...)
  87. local key_w = 30
  88. local item_fmt = ('%%-%s.%ss'):format(key_w,key_w)..'%s%-8s │%-25s'
  89. local function makeItem(key, sval, vt)
  90.     local k = valfmt(key,'list')
  91.     local border = k:len()<=key_w and '│' or '…'
  92.     --local a,b=pcall(format, k, border, vt, sval)
  93.  
  94.     if true then
  95.         --error(1)
  96.         --far.Show(tostring(k), border, vt, sval)
  97.     end
  98.     return {
  99.         text    = item_fmt:format(k, border, vt, sval),
  100.         key     = key,
  101.         type    = vt,
  102.         checked = vt=='table' and '≡'
  103.                   or vt=='function' and '˜'
  104.     }
  105. end
  106.  
  107. -- create sorted menu items with associated keys
  108. local function makeMenuItems(obj)
  109.     local items = {}
  110.  
  111.     -- grab all 'real' keys
  112.     for key in pairs(obj) do
  113.         local sval, vt = valfmt(obj[key],'list')
  114.         if not omit[vt] then
  115.             table.insert(items, makeItem(key, sval, vt))
  116.         end
  117.     end
  118.  
  119.     -- Far uses some properties that in fact are functions in obj.properties
  120.     -- but they logically belong to the object itself. It's all Lua magic ;)
  121.  
  122.     local success,props = pcall(function()return obj.properties end)
  123.     --if not success then far.Message(props,"Error in __index metamethod",nil,"wl") end
  124.     if type(props) == 'table' and not rawget(obj, 'properties') then
  125.     --if type(obj.properties) == 'table' and not rawget(obj, 'properties') then
  126.         for key in pairs(obj.properties) do
  127.             local sval, vt = valfmt(obj[key],'list')
  128.             if not omit[vt] then
  129.                 table.insert(items, makeItem(key, sval, vt))
  130.             end
  131.         end
  132.     end
  133.  
  134. --  table.sort(items, function(v1, v2)  return v1.text < v2.text end)
  135. --[[
  136.     table.sort(items, function(v1, v2)
  137.         if tables_first and (v1.type=='table') ~= (v2.type=='table') then
  138.             return v1.type=='table'
  139.         else
  140.             return v1.text < v2.text
  141.         end
  142.     end)
  143. --]]
  144. ---[[
  145.     table.sort(items, function(v1, v2)
  146.         if tables_first and v1.type~=v2.type then
  147.             return v1.type=='table' or v2.type~='table' and v1.type<v2.type
  148.         else
  149.             return v1.text < v2.text
  150.         end
  151.     end)
  152. --]]
  153.     return items
  154. end
  155.  
  156. local function luaexp_prompt(Title,Prompt,Src)
  157.     repeat
  158.         local expr = far.InputBox (nil, Title:gsub('&','&&',1), Prompt,
  159.                                    Title, Src, nil, nil, far.Flags.FIB_ENABLEEMPTY)
  160.         if not expr then return end
  161.         local f,err = loadstring('return '..expr)
  162.         if f then
  163.             local res = {pcall(f)}
  164.             if res[1] then  table.remove(res,1);    return res,expr
  165.             else    far.Message(res[2],'Error',nil,'w')
  166.             end
  167.         else
  168.             far.Message(err,'Error in expression',nil,'wl')
  169.         end
  170.     until false
  171. end
  172.  
  173. -- edit or remove object at obj[key]
  174. local function editValue(obj, key, title, del)
  175.     if del then
  176.         if 1 == far.Message(('%s is a %s, do you want to remove it?'):format(valfmt(key),
  177.             type(obj[key]):upper()),
  178.             'REMOVE: ' .. title,
  179.             ';YesNo',
  180.             'w') then
  181.             obj[key] = nil
  182.         end
  183.     else
  184.         local v, t = valfmt(obj[key], "edit")
  185.         if t == 'table' or t == 'function' then v = ''  end
  186.         local prompt = ('%s is a %s, type new value as Lua code'):format(valfmt(key),t:upper())
  187.         local res = luaexp_prompt('EDIT: ' .. title, prompt, v)
  188.         obj[key] = res and res[1] or obj[key]
  189.     end
  190. end
  191.  
  192. -- add new element to obj
  193. local function insertValue(obj, title)
  194.     local res = luaexp_prompt('INSERT: ' .. title,
  195.                               'type the key and value comma separated as Lua code')
  196.     if res then
  197.         local k, v = unpack(res)
  198.         if k~=nil then  obj[k] =end
  199.     end
  200. end
  201.  
  202. local function concat(t,delim)
  203.     local s = tostring(t[1])
  204.     for i=2,#t do s = s..delim..tostring(t[i]) end
  205.     return s
  206. end
  207.  
  208. local function getfParamsNames(f)
  209.     if not jit then return '...'    end
  210.     local info = debug.getinfo(f)
  211.     local params = {}
  212.     for i=1,info.nparams or 1000 do
  213.         local k = debug.getlocal (f, i)
  214.         if not k then   return params, #params, error();    end --impossible in lua 5.2 or luajit 2
  215.         params[i] = k
  216.     end
  217.     if info.isvararg then   params[#params+1] = '...'   end
  218.     local paramstr = #params>0 and table.concat(params,', ') or '<none>'
  219.     return paramstr, params
  220. end
  221.  
  222. -- show a menu whose items are associated with the members of given object
  223. local function process(obj, title, action)
  224.     title = title or ''
  225.     if action then brkeys[action]({obj}, 1, title); return  end
  226.  
  227.     local items, mprops = {}, {Id = uuid, Bottom = 'F1, F3, F4, Del, Ctrl+M',
  228.                                Flags={FMENU_SHOWAMPERSAND=1,FMENU_WRAPMODE=1}}--,FMENU_AUTOHIGHLIGHT=1}}
  229.     local otype = type(obj)
  230.     local item, index
  231.  
  232.     -- some member types, need specific behavior:
  233.     -- tables are submenus
  234.  
  235.     -- functions can be called
  236.     if otype == 'function' then
  237. --[[
  238.         local args,argstr
  239.           = luaexp_prompt(('CALL: %s (%s)'):format(title,getfParamsNames(obj)),
  240.                            'Type arguments as Lua code or leave empty:')
  241. --]]
  242. ---[[
  243.         local args,argstr = luaexp_prompt('CALL:'..title,
  244.                             ('arguments: %s (type as Lua code or leave empty)')
  245.                              :format(getfParamsNames(obj)))
  246. --]]
  247.         if args then
  248.             -- overwrite the function object with its return values
  249.             local res = {pcall(obj, unpack(args))}
  250.             if res[1] then
  251.                 table.remove(res,1)
  252.                 obj = res
  253.                 title = ('%s(%s)'):format(title,argstr)
  254.             else
  255.                 far.Message(([[
  256. function:  %s
  257. arguments: %s
  258. values:    %s
  259.  
  260. %s]]):format(title,argstr,concat(args,', '),res[2]),'Error',nil,'wl')
  261.                 return
  262.             end
  263.         else
  264.             return
  265.         end
  266.  
  267.     -- other values are simply displayed in a message box
  268.     elseif otype ~= 'table' then
  269.         local value = valfmt(obj,"view")
  270.         far.Message(value, title:gsub('&','&&',1), nil, value:match'\n' and 'l' or '')
  271.         return
  272.     end
  273.  
  274.     -- show this menu level again after each return from a submenu/function call ...
  275.     repeat
  276.         items = makeMenuItems(obj)
  277.         mprops.Title = title .. '  (' .. #items .. ')' .. (omit['function'] and '*' or '')
  278.  
  279.         item, index = far.Menu(mprops, items, brkeys)
  280.         mprops.SelectIndex = index
  281.  
  282.         -- show submenu/call function ...
  283.         if item then
  284.             local key = item.key or (index > 0 and items[index].key)
  285.             if item.key ~= nil then
  286.                 process(obj[key], title .. '.' .. tostring(key))
  287.             elseif item.action then
  288.                 if "break"==item.action(obj, key, (title .. '.' .. tostring(key))) then return  end
  289.             end
  290.         end
  291.         -- until the user is bored and goes back ;)
  292.     until not item
  293. end
  294.  
  295. local function getAllUpvalues(f)
  296.     local upvalues = {}
  297.     for i=1,1000 do
  298.         local k,v = debug.getupvalue (f, i)
  299.         if not k then   return upvalues, i-1    end
  300.         upvalues[k] = v
  301.     end
  302. end
  303.  
  304. local function syncUpvalues(f,t,n)
  305.     for i=n,1,-1 do
  306.         local k,v = debug.getupvalue (f, i)
  307.         if t[k]~=v then
  308.             assert(k == debug.setupvalue (f, i, t[k]))
  309.         end
  310.     end
  311. end
  312.  
  313. local function getAllLocals(level)
  314.     local locals = {}
  315.     for i=1,1000 do
  316.         local k,v = debug.getlocal (level+1, i)
  317.         if not k then   return locals, i-1  end
  318.         locals[k] = v
  319.     end
  320. end
  321.  
  322. local function syncLocals(level,t,n)
  323.     level = level + 1
  324.     for i=n,1,-1 do
  325.         local k,v = debug.getlocal (level, i)
  326.         if t[k]~=v then
  327.             assert(k == debug.setlocal (level, i, t[k]))
  328.         end
  329.     end
  330. end
  331.  
  332. local function getVararg(level)
  333.     local vararg = {}
  334.     for i=1,1000 do
  335.         local k,v = debug.getlocal (level+1, -i)
  336.         if not k then   return vararg   end--(*vararg)
  337.         vararg[i] = v
  338.     end
  339. end
  340.  
  341. local function syncVararg(level,t)
  342.     for i=1,#t do
  343.         local k,v = debug.getlocal (level+1, -i)
  344.         if v~=t[i] then debug.setlocal (level+1, -i, t[i])  end
  345.     end
  346. end
  347.  
  348. local function getLocalsAndParams(level)
  349.     local locals,n = getAllLocals(level+1)
  350.     local info = debug.getinfo(level+1)
  351.     local vararg = getVararg(level+1)
  352.     vararg = vararg[1] and vararg
  353.     locals['(*vararg)'] = locals['(*vararg)'] or vararg or nil
  354.     local info = debug.getinfo(level+1)
  355.     local name = ('(*func: %s)'):format(info.name or '<noname>')
  356.     locals[name] = locals[name] or info.func
  357.     return locals,n,vararg
  358. end
  359.  
  360. local function showLocals(level,shift)
  361.     --far.Show(level,shift)
  362.     if not shift then
  363.         shift = 0
  364.         for i = 1,1000 do
  365.             local info = debug.getinfo(i,'f')
  366.             if not info then    break   end
  367.             if info.func==process then  shift = i   end
  368.         end
  369.         if shift>900 then   return  end
  370.     end
  371.     level = level + shift
  372.     local info = debug.getinfo(level,'')
  373.     if not info then    mf.beep() return    end
  374.     local locals,n,vararg = getLocalsAndParams(level)
  375.     if n>0 or vararg  then
  376.         process(locals, ('locals [%d]: %s'):format(level-shift,info.name or 'main chunk'))
  377.         syncLocals(level,locals,n)
  378.         if vararg then syncVararg(level,vararg) end
  379.         return 'break'
  380.     end
  381. end
  382.  
  383. brkeys = {
  384.     {BreakKey = 'F9',   action = function(info)
  385.         process(debug.getregistry(), 'debug.getregistry:')
  386.     end;    name = 'registry'},
  387.  
  388.     {BreakKey = 'Ctrl+1',   action = function() return showLocals(1)    end;    name = 'locals'},
  389.     {BreakKey = 'Ctrl+2',   action = function() return showLocals(2)    end;    name = 'locals2'},
  390.     {BreakKey = 'Ctrl+3',   action = function() return showLocals(3)    end;    name = 'locals3'},
  391.     {BreakKey = 'Ctrl+4',   action = function() return showLocals(4)    end;    name = 'locals4'},
  392.     {BreakKey = 'Ctrl+5',   action = function() return showLocals(5)    end;    name = 'locals5'},
  393.     {BreakKey = 'Ctrl+6',   action = function() return showLocals(6)    end;    name = 'locals6'},
  394.     {BreakKey = 'Ctrl+7',   action = function() return showLocals(7)    end;    name = 'locals7'},
  395.     {BreakKey = 'Ctrl+8',   action = function() return showLocals(8)    end;    name = 'locals8'},
  396.     {BreakKey = 'Ctrl+9',   action = function() return showLocals(9)    end;    name = 'locals9'},
  397.     {BreakKey = 'Ctrl+0',   action = function() return showLocals(0)    end;},
  398.     {BreakKey = 'Ctrl+G',   action = function() process(_G,'_G');   return "break"  end},
  399.  
  400.     {BreakKey = 'Ctrl+Insert',  action = function(obj, key)
  401.         far.CopyToClipboard ((valfmt(obj[key])))
  402.     end},
  403.  
  404.     {BreakKey = 'CtrlShift+Insert', action = function(obj, key)
  405.         far.CopyToClipboard ((valfmt(key)))
  406.     end},
  407.  
  408.     {BreakKey = 'Ctrl+Up',  action = function(obj, key, kpath)
  409.         local f = obj[key]
  410.         if type(f) == 'function' then
  411.             local t,n = getAllUpvalues(f)
  412.             if n>0 then
  413.                 process(t, 'upvalues: ' .. kpath)
  414.                 syncUpvalues(f,t,n)
  415.             end
  416.         end
  417.     end;    name = 'upvalues'},
  418.  
  419.     {BreakKey = 'Ctrl+Down',    action = function(obj, key, kpath)
  420.         local f = obj[key]; local t = type(f)
  421.         if t=='function' or t=='userdata' or t=='thread' then
  422.             local env = debug.getfenv(f)
  423.             if (env~=_G or 1==far.Message('_G','Show global environment?',';OkCancel'))
  424.                   and next(env) then
  425.                 process(env, 'getfenv: ' .. kpath)
  426.             end
  427.         end
  428.     end;    name = 'env'},
  429.  
  430.     {BreakKey = 'Ctrl+Right',   action = function(obj, key, kpath)
  431.         local f = obj[key]
  432.         if type(f) == 'function' then
  433.             local args,t = getfParamsNames(f)
  434.             if args:len()>0 then
  435.                 process(t, 'params (f): ' .. kpath)
  436.                 local name = debug.getinfo(f).name
  437.                 --far.Message(('%s (%s)'):format(name or kpath,args), 'params')
  438.             end
  439.         end
  440.     end;    name = 'params'},
  441.  
  442.     {BreakKey = 'Alt+F4',   action = function(obj, key, kpath)
  443.         local f = obj[key]
  444.         if type(f) == 'function' then
  445.             local info = debug.getinfo(f,'S')
  446.             local filename = info.source:match("^@(.+)$")
  447.             if filename then
  448.                 editor.Editor(filename,nil,nil,nil,nil,nil,nil,info.linedefined)
  449.             end
  450.         end
  451.     end; name = 'edit'},
  452.  
  453.     {BreakKey = 'F3',   action = function(obj, key, kpath)
  454.         local f = obj[key]
  455.         if type(f) == 'function' then
  456.             process(debug.getinfo(f), 'debug.getinfo: ' .. kpath)
  457.         elseif type(f) == 'thread' then
  458.             far.Message(debug.traceback(f,"level 0",0),'debug.traceback: ' .. kpath,nil,'l')
  459.             --far.Show('debug.traceback: ' .. kpath .. debug.traceback(f,", level 0",0))
  460.         end
  461.     end;    name = 'info'},
  462.  
  463.     {BreakKey = 'F4',   action = function(obj, key, kpath)
  464.         return key ~= nil and editValue(obj, key, kpath)
  465.     end},
  466.  
  467.     {BreakKey = 'Ctrl+F',   action = function()
  468.         omit['function'] = not omit['function']
  469.     end},
  470.  
  471.     {BreakKey = 'Ctrl+T',   action = function()
  472.         tables_first = not tables_first
  473.     end},
  474.  
  475.     {BreakKey = 'Ctrl+M',   action = function(obj, key, kpath)
  476.         local mt = key ~= nil and debug.getmetatable(obj[key])
  477.         return mt and process(mt, 'METATABLE: ' .. kpath)
  478.     end;    name = 'mt'},
  479.  
  480.     {BreakKey = 'DELETE',   action = function(obj, key, kpath)
  481.         return key ~= nil and editValue(obj, key, kpath, true)
  482.     end},
  483.  
  484.     {BreakKey = 'INSERT',   action = function(obj, key, kpath)
  485.         insertValue(obj, kpath:sub(1, -(#tostring(key) + 2)))
  486.     end},
  487.  
  488.     {BreakKey = 'F1',   action = function()
  489.         far.Message(help,'Lua Explorer - Help',nil,'l')
  490.     end},
  491. }
  492.  
  493. -- if LuaJIT is used, maybe we can show some more function info
  494. if jit then
  495.     table.insert(brkeys,    {BreakKey = 'Shift+F3', action = function(obj, key, kpath)
  496.         if key ~= nil and type(obj[key]) == 'function' then
  497.             process(jit.util.funcinfo(obj[key]), 'jit.util.funcinfo: ' .. kpath)
  498.         end
  499.     end;    name = 'jitinfo'})
  500. end
  501.  
  502. for i=1,#brkeys do
  503.     local bk = brkeys[i];   if bk.name then brkeys[bk.name] = bk.action end
  504. end
  505.  
  506. if not Macro then return process end
  507.  
  508. Macro { description = "Lua Explorer";
  509.     area="Common"; key="CtrlShiftF12"; action=function()
  510.         process(_G,'_G')
  511.         --require"le"(_G,'_G')
  512.     end
  513. }
  514.  
  515. -- it's possible to call via lm:post, e.g. from user menu:
  516. -- lm:post dofile(win.GetEnv("FARPROFILE")..[[\Macros\scripts\le.lua]])(_G,'_G')
  517. -- lm:post require"le"(_G,'_G')
Add Comment
Please, Sign In to add comment