Simple Lua Cross-references

I found a need to reference functions that I've defined in the global scope so I added an index for top-level function/variable definitions in space-lua blocks. I literally just cobbled this together but thought it might be useful for others in its rough form.

Here's a simple widget:

lua = lua or {}
function lua.ref(name)
  local refs = query[[from l = index.tag "lua" where l.name == name order by l.priority asc limit 1 select l.ref]]
  if not some(refs) then
    return widget.markdown("Lua object " .. name .. " not found")
  end
  return widget.markdown("[[" .. refs[1] .. "|" .. name .. "]]")
end

Example usage:

This a ref to ${lua.ref("lua.ref")}.

Here's the indexer: ${lua.ref("indexPageLua")}.

A handy command:

commands = commands or {}
function commands.luaPicker()
  local refs = query[[from l = index.tag "lua" select {name=l.name, value=l.ref}]]
  local result = editor.filterBox("Select:", refs)
  if some(result) then
    editor.navigate(result.value)
  end
end

command.define {
  name = "Navigate: Lua Picker",
  run = commands.luaPicker,
}

And here's the page:index handler:

function table.extend(tbl, vals)
  for _, val in ipairs(vals) do
    table.insert(tbl, val)
  end
  return tbl
end

local function findNodes(tree, f)
  if not tree then
    return {}
  end
  if f(tree) then
    return {tree}
  end
  if not tree.children then
    return {}
  end
  local res = {}
  for _, c in ipairs(tree.children) do
    table.extend(res, findNodes(c, f))
  end
  return res
end

local function findSpaceLua(tree)
  local codes = findNodes(tree, function(tree) return tree.type == "FencedCode" end)
  local res = {}
  for _, code in ipairs(codes) do
    if some(findNodes(code, function(tree) return tree.type == "CodeInfo" and tree.children and tree.children[1].text == "space-lua" end)) then
      table.extend(res, findNodes(code, function(tree) return tree.type == "CodeText" end))
    end
  end
  return res
end

local function varName(var)
  if var.name then
    return var.name
  end
  if var.type == "PropertyAccess" then
    return table.concat({varName(var.object), var.property}, ".")
  end
  print("Unknown variable type:", var)
end

function indexSpaceLua(codeText, page)
  local text = codeText.children[1].text
  local priority = string.matchRegex(text, "\\s*--+\\s*priority:\\s*(-?\\d+)")
  if priority then
    priority = math.tointeger(priority[2])
  end
  local tree = lua.parse(text)
  local tags = {}
  for _, stmt in ipairs(tree.statements) do
    if stmt.type == "Function" then
      table.insert(tags, {
        tag = "lua",
        page = page,
        kind = stmt.type,
        name = table.concat(stmt.name.propNames, "."),
        priority = priority,
        ref = page .. "@" .. tostring(stmt.ctx.from + codeText.from)
      })
    elseif stmt.type == "Assignment" then
      for _, var in ipairs(stmt.variables) do
        table.insert(tags, {
          tag = "lua",
          page = page,
          kind = var.type,
          name = varName(var),
          priority = priority,
          ref = page .. "@" .. tostring(var.ctx.from + codeText.from)
        })
      end
    end
  end
  return tags
end

function indexPageLua(page, tree)
  tree = tree or markdown.parseMarkdown(space.readPage(page))
  local blocks = findSpaceLua(tree)
  local allTags = {}
  for _, block in ipairs(blocks) do
    table.extend(allTags, indexSpaceLua(block, page))
  end
  return allTags
end

event.listen {
  name = "page:index",
  run = function(e)
    index.indexObjects(e.data.name, indexPageLua(e.data.name, e.data.tree))
  end
}
1 Like