Skip to content

Instantly share code, notes, and snippets.

@felixr
Last active October 18, 2025 17:29
Show Gist options
  • Select an option

  • Save felixr/9e713d96c5165bc2f32ea6a0fa9786da to your computer and use it in GitHub Desktop.

Select an option

Save felixr/9e713d96c5165bc2f32ea6a0fa9786da to your computer and use it in GitHub Desktop.
Lua formatter for silverbullet

Lua Formatter

lua_formatter = lua_formatter or { initDone = false }

-- Not using @johnnymorganz/[email protected]/stylua.web/stylua_lib.js
-- because of https://github.com/JohnnyMorganz/StyLua/issues/999

local stylua = js.import(
  "https://cdn.jsdelivr.net/npm/@isentinel/[email protected]/stylua.web/stylua_lib.min.js"
)

local getChild = function(node, type)
  for _, child in ipairs(node.children) do
    if child.type == type then return child end
  end
end

local getText = function(node)
  if not node then return nil end
  if node.text then
    return node.text
  else
    for _, child in ipairs(node.children) do
      local text = getText(child)
      if text then return text end
    end
  end
end

-- Note: the return value of this can only be used once.
-- You cannot store and reuse the config object, because
-- it is automatically freed after its use.
local getConfig = function()
  -- Workaround to call a static method on the Config class.
  local newConfig = js.window.eval("(x) => x.Config.new()")
  local cfg = newConfig(stylua)
  lua_formatter.config = cfg
  cfg.column_width = 80
  cfg.indent_type = stylua.IndentType.Spaces
  cfg.indent_width = 2
  cfg.collapse_simple_statement = stylua.CollapseSimpleStatement.Always
  return cfg
end

local maybeInit = function()
  if not lua_formatter.initDone then
    -- This loads the WASM module
    lua_formatter.module = stylua.default()
    lua_formatter.initDone = true
  end
end

local getCodeBlock = function()
  local text = editor.getText()
  local tree = markdown.parseMarkdown(text)
  local cursorPos = editor.getCursor()

  -- find the FencedCode node with lua code that the cursor is currently in
  local findMyFence = function()
    local node = tree
    if not node.children then return nil end
    while node.to >= cursorPos do
      for _, child in ipairs(node.children) do
        if child.from <= cursorPos and child.to >= cursorPos then
          if node.type == "FencedCode" then
            local info = getText(getChild(node, "CodeInfo"))
            if info and info:find("lua") then
              return getChild(node, "CodeText")
            end
            editor.flashNotification(
              "[Lua Format] Code has wrong type: " .. info,
              "warning"
            )
            return nil
          end
          node = child --  traverse into node
          break --  for loop
        end
      end --for
    end -- while
  end

  local fence = findMyFence()
  if fence then return fence.children[1] end
  editor.flashNotification(
    "[Lua Format] Doing nothing. Not in a space-lua fence.",
    "warning"
  )
  return nil
end

function lua_formatter.format()
  local line = editor.getCursor()
  local block = getCodeBlock()
  if not block then return end

  maybeInit()
  local formatted = stylua.formatCode(block.text, getConfig())
  editor.replaceRange(block.from, block.to, formatted)
  editor.flashNotification("Lua code block formatted", "info")
  editor.moveCursor(line)
end

command.define({
  name = "Editor: Lua Format",
  run = lua_formatter.format,
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment