Tool I created to help me explore and learn Lua and Codea

I was having a hard time understanding the structure of the Lua environment, so I built a tool to help me easily explore Global variables and functions in Codea. It lets you easily navigate through the Global Environment _ENV using the parameters panel. It also lets you see the current value of global variables you have set.

It really helped me understand Codea and Lua, and also helped with debugging some of my apps as well. So, I thought I should share.

I feel like I am grossly abusing the parameters and output panel, but it was easier than creating an overlay and buttons. If you are using parameters in your app then it will overwrite them. I found it useful to start a new project and just explore. Setup is simple; just paste the code in a blank file and run your project. If you feel your global variable is missing from the list, press “Refresh List”.

I hope this is of some use to someone.

Edit: Added support for non-key tables.
Edit 2: After playing around with this to make sure the code was ok and fixing some small mistakes. I noticed an added bonus of using the parameter’s area for debug; it still works when the app is paused using Codea’s pause button! Also, added cleaner menu and hiding of CONSTANTS.
Edit 3: Made current path to variable more clear and visible
Edit 4: I added Live Viewing/Watching of the currently selected variable. Test it out by choosing ElapsedTime.

--[[
Debug Tool for viewing and exploring all Global Variables

## Usage:

- Load this project as a dependency or copy this code to a blank lua file in your project.
- This is a tool I made to help me learn Lua better and explore the environment.
- If a variable is a table then ">>" will be by its name. Click the button to go deeper.
- If a variable is a function then "-> f()" will be by its name. Click for function debug info.
- Clicking any variable will give more information in the output area.
- The output is copied to the clipboard as well.
- Choose a variable to Live View it and/or press again to get a current snapshot in output.
- The app can be paused and still allow viewing
- Turn on Copy Output to Clipboard, the next variable pressed will have its value copied to clipboard.

## Warnings:

- All parameters you have set or are using will be erased.
- Local variables are not visible
- Table _G is just repeating into itself.

--]]

function viewEnv(pathKeys, showConstants, varKey)
    local pathKeys = pathKeys or {_ENV}
    
    parameter.clear()
    
    -- Button to Select between hiding or showing constants
    local c = "Hiding"
    if showConstants then c = "Showing" end
    parameter.boolean("Copy_Output_to_Clipboard", false)
    parameter.action(string.format("[ %s Constants ]", c),
    function ()
        viewEnv(pathKeys, not showConstants)
    end
    )
    
    -- Button to Refresh Variable or Check for new ones
    parameter.action("[ Refresh Variables ]",
    function ()
        viewEnv(pathKeys, showConstants)
    end
    )
    
    -- Button to move out of the current table
    if #pathKeys > 1 then
        parameter.action("[ << BACK ]",
        function ()
            table.remove(pathKeys, #pathKeys)
            viewEnv(pathKeys, showConstants)
        end
        )
    end
    
    -- Get Current Path to Variable List
    local path = "_ENV"
    local spacer = " > "
    for i = 2, #pathKeys do
        path = path .. spacer .. tostring(pathKeys[i])
    end
    parameter.watch("_Path_"); _Path_ = path
    
    local _liveVariable = "_ENV"
    local function setLiveVariable(l, v)
        if type(l) ~= "function" then       
        if #l > 0 then
            _liveVariable = string.format("%s[%s]", _liveVariable, v) 
        else
            _liveVariable = string.format('%s["%s"]', _liveVariable, v) 
        end
        end
    end
    
    -- Find current table from pathKey list
    local loc = pathKeys[1]
    for i = 2, #pathKeys do
        setLiveVariable(loc, pathKeys[i])
        loc = loc[pathKeys[i]]
    end
    
    if type(loc) ~= "function" and varKey then
        setLiveVariable(loc, varKey)
        parameter.action(string.format("Live Viewing: [%s]", varKey))
        parameter.watch(_liveVariable)
    end
    
    -- Allow exploring functions
    if type(loc) == "function" then
        loc = debug.getinfo(loc)
        loc.func = nil
    end
    
    local keys = {}
    local tableType = "Key: "
    -- Check if array or key table
    if #loc > 0 then
        tableType = "Index: "
        for i = 1, #loc do
            table.insert(keys, i)
        end
    else
        -- Convert keys to strings to sort alphabetically
        for k, _ in pairs(loc) do
            k = tostring(k)
            if (not showConstants and k ~= string.upper(k)) or showConstants then
                table.insert(keys, k)
            end
        end
        table.sort(keys, function (a, b) return string.lower(a) < string.lower(b) end)
    end
    
    -- Create the buttons for the variable
    for _, k in ipairs(keys) do
        local title = k
        
        -- Check if it is a table or function and allow going deeper
        if type(loc[k]) == "table" then
            title = k .. " >>"
        elseif type(loc[k]) == "function" then
            title = k .. " -> f()"
        end
        
        parameter.action(title,
        function()
            output.clear()
            local t = type(loc[k])
            local v = tostring(loc[k])
            
            print(tableType .. k)
            print("Value: " .. v)
            
            -- Copy Value to clipboard
            if Copy_Output_to_Clipboard then
                pasteboard.copy(v)
                print("Output value has been copied to the clipboard.")
            end
            
            if t == "table" or t == "function" then
                table.insert(pathKeys, k)
                viewEnv(pathKeys, showConstants)
            else
                viewEnv(pathKeys, showConstants, k)
            end
        end
        )
    end
    
end

-- Auto Run the Environment Viewer
viewEnv()

@exomut thanks for sharing, could be very useful

I have to give this a try, any sort of debugging aid would be great in Codea right now

@Simeon it really helped me understand a lot about Codea, especially being able to see some code for class() and tracking variables inside of my custom classes. I am still quite new to Lua, so don’t keep your hopes up on an amazing debug tool :wink: I have already edited this post’s code a couple of times because of small bugs I found.

I thought I should show it in use, since I think I am done working on it. I got distracted from my other stuff :confused: There is no setup, just include it as a dependency, or in your project. Make sure to “Refresh List” to catch new ones created after setup. Then navigate through the environment tree to the variable you want to check on. Sadly, local variables aren’t visible.

In the video, I checked on my spinner object’s rotate variable to see what angle it was at.

Back to working on my other projects now.

https://youtu.be/15nEp3OWmKA

@exomut I found this interesting and I like the way you did the parameters to investigate the entries. This reminded me of something I did in 2012. See the link below. I used _G to create the lists.

https://codea.io/talk/discussion/1865/list-of-codea-globals-constants

Thanks for the video! I’m constantly amazed by the way people use the sidebar

(It also makes me terrified because the sidebar is some of the oldest, most brittle remaining code in Codea. The plan is to replace it with the much newer architecture from Shade at some point)

@exomut You said you didn’t want to use buttons, so I modified one of my button projects to list Codea stuff. Slide the buttons up or down and tap on one (table in green) for more info. Tap the Back button (upper right) to go back a level. The line at the top shows the levels you’re in. I didn’t try to hide any of my project variables, so those can be explored. This isn’t meant to be used for debugging like yours, but just to explore Codea.

Code removed. Updated version below.

Sorry for the late reply. I have been out sick for a couple of days.

@Simeon If your new parameter panel looks anything like the picture you guys posted on Twitter, I am getting excited! That SizeOverTime graph looks extremely useful. If you could throw in a no thrills object view, kind of like what I made, it would be amazing. If not, I’ll just make it again!

@dave1707 Wow you have been on these forums for a long time. No wonder you know your way around Codea so well :slight_smile: I tried out the list code you sent. The color choices make it very easy to read and explore. I was reading up on Lua and around 2011 was Lua 5.1 where _G was for globals, but there was no directly accessible environment variable, you had to use setfenv() to work with it. From 5.2 and 5.3, I noticed _G is still used for globals, however, it points back to _ENV. If someone changed _G to point to their own private table of Globals or renamed the globals table, then the globals table wouldn’t be found. So I decided to start from _ENV. Please correct me if I have my information wrong. It was quite a confusing read. I feel like I am in way over my head sometimes.

@exomut I’ve been here since 2012 along with a few other users. There is a lot to learn with Codea and it never gets boring. You’ve only been here for a few months, but you know your way around very well.

Here’s an updated version. All of my variables and functions are now local and won’t show in the list. I added socket=require(“socket”) so that can be explored. When the code first starts, not everything is loaded because there isn’t a setup() function. To load the remaining information, press _G at the start of the list then tap the upper right BACK button.

local function dumpCodea()
    displayMode(FULLSCREEN)
    rectMode(CENTER)    
    socket=require("socket")    
    local name,addr,pos,list={"_G"},{_G},{-30},{}
    local cnt,str=1,""
    
    local function createList(key)
        str=""
        for a,b in pairs(name) do
            str=str..b.." : "
        end
        list={}    
        for a,b in pairs(key) do
            if pcall(function() local st=tostring(b) end) == false then
                table.insert(list,{name=tostring(a),strng="Caused tostring Error",
                    value=0,address=0})
            else
                table.insert(list,{name=tostring(a),strng=tostring(b),
                    value=type(b),address=b})
            end
        end    
        table.sort(list,function(a,b) 
            return string.lower(a.name)<string.lower(b.name) end)
    end  
          
    function draw()
        rectMode(CENTER)
        background(0)    
        for z=1,#list do      
            fill(15, 213, 249, 200)
            rect(200,HEIGHT-z*40+pos[cnt],300,40)
            fill(174, 222, 193, 255)
            if list[z].value=="table" then
                fill(24, 255, 0, 255)
            end
            rect(500,HEIGHT-z*40+pos[cnt],300,40)
            fill(255)
            text(list[z].name,200,HEIGHT-z*40+pos[cnt])
            fill(255,0,0)
            text(list[z].strng,500,HEIGHT-z*40+pos[cnt])
        end
        fill(255)
        text("BACK\
Level "..cnt,WIDTH-50,HEIGHT-50)
        if #list==0 then
            text("EMPTY TABLE",WIDTH/2,HEIGHT/2)
        end
        rect(WIDTH/2,HEIGHT-20,500,40)
        fill(0, 12, 255, 255)
        text(str,WIDTH/2,HEIGHT-20)
    end
    
    function touched(t)
        if t.state==BEGAN then
            if t.x>WIDTH-100 and t.y>HEIGHT-100 and cnt>1 then
                addr[cnt]=nil
                cnt=cnt-1
                table.remove(name,#name)
                createList(addr[cnt])
            end
            selectState="began"
        elseif t.state==MOVING then
            selectState="moving"
            pos[cnt]=pos[cnt]+t.deltaY
            if pos[cnt]<-30 then
                pos[cnt]=-30
            end
        elseif t.state==ENDED and selectState=="began" then
            for z=1,#list do
                if t.x>1 and t.x<650 and 
                        t.y>HEIGHT-z*40-40/2+pos[cnt] and 
                        t.y<HEIGHT-z*40+40/2+pos[cnt] then 
                    if list[z].value=="table" then 
                        table.insert(name,list[z].name)
                        cnt=cnt+1
                        addr[cnt]=list[z].address
                        pos[cnt]=-30
                        createList(addr[cnt])
                    end
                end
            end
        end
    end
    
    createList(addr[cnt])
end

dumpCodea()