Codea written in Codea

Some of you might get a kick out of this. After watching Bret Victor’s talk I spent a few hours writing an “instant” version of Codea inside Codea. Basically it gives you a keyboard and you can write Codea code, any code you write is instantly run and drawn behind your code.

It’s pretty interesting. Here’s the code and a screenshot.

Codea Instant

Main file is Here: http://pastebin.com/8enSYdgZ — does not paste into forums due to Emoji.

Buffer


Buffer = class()

function Buffer:init()
    self.buffer = {}
    
    self.font = "Inconsolata"
    self.fontSize = 20
    
    -- x = line, y = pos
    self.cursor = vec2(1,1)
    
    self.t = 0
    self.cursorBlink = 0
end

function Buffer:setStyle()
    textMode(CORNER)
    font( self.font )
    fill(255)
    fontSize( self.fontSize )
    textWrapWidth(10000)
end

function Buffer:cursorToScreen(c)
    -- Get a subset of the buffer
    local lines = table_slice( self.buffer, 1, c.x )
    local l = self.buffer[c.x]
    
    local upToStr = self:bufferToString(lines)
    local lstr = nil
    
    if l then
        lstr = table.concat(l)
    end
    
    pushStyle()
    
    self:setStyle()
    
    local lw = 0
    local _,lh = textSize("A")
    local emptyLine = true
    
    if lstr and lstr ~= "" then
        lw,lh = textSize(string.sub(lstr,1,c.y - 1))
        emptyLine = false
    end
    
    local pw,ph = textSize(upToStr)
    
    if emptyLine then
        ph = ph + lh
    end
    
    popStyle()
    
    return lw,(ph - lh/2)
end

function Buffer:bufferToString(b)
    local bstrings = {}
    for k,v in pairs(b) do
        table.insert(bstrings, table.concat( b[k] ) )
    end
    
    return table.concat( bstrings, "\
" )
end

function Buffer:moveCursor(o)
    self.cursor = self.cursor + o
    
    self.cursor.x = math.max(1, math.min(self.cursor.x, #self.buffer))
    
    local l = self.buffer[self.cursor.x]
    local y = self.cursor.y
    
    y = math.max(1, math.min(y, #l + 1))
    self.cursor.y = y
end

function Buffer:insertCharacter(c)
    local l = self.buffer[self.cursor.x]
    
    if l == nil then
        l = {}
        self.buffer[self.cursor.x] = l
    end

    if c == "\
" then
        local start = table_slice(l,1,self.cursor.y)
        local tail = table_slice(l,self.cursor.y+1,#l)
        
        table[self.cursor.x] = start
        table.insert(self.buffer, self.cursor.x+1, tail)
        
        self.cursor = vec2( self.cursor.x + 1, 1 )
    elseif c == BACKSPACE then
        if self.cursor.y == 1 then
            -- delete line
            local prevLine = self.buffer[self.cursor.x - 1]
            
            table.remove(self.buffer, self.cursor.x)
            
            if prevLine then
                self.cursor = vec2(self.cursor.x - 1, #prevLine + 1)
                table_append( prevLine, l )
            end
        else
            -- delete character
            table.remove(l, self.cursor.y - 1 )
            self.cursor = vec2(self.cursor.x, self.cursor.y - 1 )
        end
    else
        table.insert(l, self.cursor.y, c)
    
        self.cursor = vec2( self.cursor.x, self.cursor.y + 1 )
    end
end

function Buffer:draw()
    self.t = self.t + 8 * DeltaTime
    self.cursorBlink = (math.sin(self.t) + 1) * 128
    
    pushStyle()
    
    self:setStyle()
    
    local str = self:toString()
    local w,h = textSize(str)
    
    pushMatrix()
    translate( 40, -40 )
    
    text(str, 0, HEIGHT - h)
    
    -- Draw cursor
    -- Cursor pos x,y
    local cpx,cpy = self:cursorToScreen(self.cursor)
    fill(0, 87, 255, self.cursorBlink)
    rectMode(CENTER)
    rect(cpx,HEIGHT - cpy,5,22)
    
    popMatrix()
    popStyle()
end

function Buffer:clear()
    self.cursor = vec2(1,1)
    self.buffer = {}
end

function Buffer:toString()
    return self:bufferToString(self.buffer)
end

function Buffer:toStringWithoutActiveLine()
    local bstrings = {}
    for k,v in pairs(self.buffer) do
        if k ~= self.cursor.x then
            table.insert(bstrings, table.concat( self.buffer[k] ) )
        end
    end
    
    return table.concat( bstrings, "\
" )
end

EmButton

-- Emoji Button
EmButton = class()

function EmButton:init(pos,txt)
    -- you can accept and set parameters here
    self.pos = pos
    self.text = txt
    self.action = nil
    self.highlight = false
    self.color = color(255,255,255,255)
end

function EmButton:size()
    pushStyle()
    self:setStyle()

    local w,h = textSize(self.text)

    popStyle()
    
    return w,h
end

function EmButton:hitTest(lp)
    local w,h = self:size()
    
    local left,right = self.pos.x - w/2, self.pos.x + w/2
    local top,bottom = self.pos.y + h/2, self.pos.y - h/2
    
    if lp.x > left and lp.x < right and
       lp.y > bottom and lp.y < top then
        return true
    end
    
    return false
end

function EmButton:setStyle()
    fill(self.color)
    noStroke()
    textMode(CENTER)
    fontSize(50)
    font("AppleColorEmoji")
end

function EmButton:draw()
    pushMatrix()
    
    translate(self.pos.x,self.pos.y)
    
    pushStyle()
    self:setStyle()
    
    text(self.text,0,0)

    popStyle()
    
    popMatrix()
end

function EmButton:touched(touch)
    if touch.state == ENDED then
        -- Tapped
        if self:hitTest( vec2(touch.x,touch.y) ) then
            if self.action then self.action() end
        end
    end
end

Util

function table_append (t1, t2)
    local t1s = #t1
    for k,v in pairs(t2) do t1[k + t1s] = v end
end

function table_slice (values,i1,i2)
    local res = {}
    local n = #values
    
    -- default values for range
    i1 = i1 or 1
    i2 = i2 or n
    if i2 < 0 then
        i2 = n + i2 + 1
    elseif i2 > n then
        i2 = n
    end

    if i1 < 1 or i1 > n then
        return {}
    end

    local k = 1
    
    for i = i1,i2 do
        res[k] = values[i]
        k = k + 1
    end
    
    return res
end

Main file is Here: http://pastebin.com/8enSYdgZ — does not paste into forums due to Emoji.

oh nice job, that’s fun !

Going to check that code out, thanks for sharing

I was thinking of experimenting on my own projects by hacking outside the sandbox, to see if in concept works first. I remember you had posted a lua interpreter in lua before, and might be able to use that…I think this is my next codea project :slight_smile:

It’s awesome!

Will inspect this code later - are you using loadstring?

How much more would we need to convert this to a primitive codea debugger?

It does use loadstring(). I’m not too sure how it could be converted into a debugger — if you think of anything it would be interesting to hear.

Perhaps we need to add some more meta-methods to Codea, like reading and writing to your project’s buffers.

I knew someone will write something like this. I thought it’s gonna be done first by @bortels as he’s kinda an adventurer coder (is that term existed?). :smiley:

This is very basic but very fun nevertheless. I think it would interest kids as it’s more interactive. I’m gonna give it to my 5 y.o son and see how he respons this. Thank you, @simeon. :slight_smile:

Just before I saw this post (while bored in science), I had thought about using my file io hack to do this. Mabe I’ll alter the code a little to allow you o select the project to edit… Feel free to take my idea.

local pKey = EmButton(vec2(0,0), "A")
    pKey.action = function() 
        if displayMode() == STANDARD then
            displayMode(FULLSCREEN_NO_BUTTONS) 
        else displayMode(STANDARD) 
            print(buffer:toString()) end 
    end
    
    keys = {closeKey,startKey,leftKey,
            rightKey,endKey,upKey,
            downKey,keyKey,pKey}

minor addition to main to allow cut/paste

Nice change @Ipda41001.

@John also made a really good change by using pcall() to execute the code block, and only executing the last-working-block. This allows it to ignore runtime errors and always draw something on the screen.

Love this!

Here’s code using the pcall idea to catch errors.

    local chunk, loadErr = loadstring(str)
    
    if chunk then
        local status, err = pcall(chunk)
        if status then
            lastGood = chunk
            clearOutput()
            print("allgood")
        else
            clearOutput()
            print(err)
        end
    else
        if lastGood then
            pcall(lastGood)
        end
        
        clearOutput()
        print(loadErr)
    end

Run it non-fullscreen and move the output window up to see error feedback as you type.

Cool. And I also found a big that crashes Codea when you try and delete a character that’s not there.

Simeon you used a function “chunk()”. Is that a lua function? What it actually does? I figured out it executes the command we typed but Where can I learn about it more?

@rashedlatif “chunk” was my choice of variable name. Basically the loadstring() function accepts an arbitrary “chunk” of Lua code and returns it as an executable “chunk”. I store that in a variable called “chunk” which I later execute by putting function call parentheses after the variable name (i.e. chunk()).

However @dylanf’s method is much, much better. Using the built in Lua function pcall() to execute the chunk isolates run-time errors so that they don’t crash or interrupt the app.

Makes Sence :slight_smile: thanks Simeon. I m still very beginner in lua. But no doubt these kinds of features are very powerful ones.

This is very cool @Simeon!

Heh, MetaCodea.

Hi, sorry for asking this (I’m a newbie): what does the ‘current’ version of this ‘MetaCodea’ look like? I mean, with the suggested additions from @Ipad41001, @John & @dylanf. Thanks in advance,
Victor ~O)

Edit: as usual, I’ve already found it by myself (the best way to get the basics), except the one from @John mentioned by @Simeon. :smiley:

Tiny bugs: cursor takes a lower position, when at the left of the code lines, and blinks incorrectly when a program executes. I’ve checked with this tiny piece of code:

function draw()
    ellipse(300,500,50)
end

Tiny suggestion: to cache written code and to add a “stop execution” button.

Wow, codeseption, we need to go deeper. This could be useful.