#meta
Implements utilities for working with Markdown tables


Table

Commands


command.define {
  name = "Table: Format",
  run = function()
    position = editor.getCursor()
    tree = markdown.parseMarkdown(editor.getText())
    tableNode = nodeParentOfType(tree, position, "Table")
    if tableNode then
      formatMarkdownTable(tableNode)
    else
      editor.flashNotification("This command must be run with cursor in a table", "error")
    end
  end
}


Implementation


-- Find outermost node of given type that contains the position
-- similar to plug-api/lib/tree.ts nodeAtPos
function nodeParentOfType(tree, position, nodeType)
  if position < tree.from or position > tree.to then
    return nil
  end
  if tree.type == nodeType then
    return tree
  end
  if tree.children then
    for _, child in ipairs(tree.children) do
      if position >= child.from and position <= child.to then
        return nodeParentOfType(child, position, nodeType)
      end
    end
  end
  return nil
end


function formatMarkdownTable(tree)
  -- First find the desired length for each column
  columnLengths = {}
  columnAlignments = {}
  for _, child in ipairs(tree.children) do
    if child.type == "TableDelimiter" then
      column = 1
      for _, spec in ipairs(string.split(child.children[1].text, "|")) do
        if #spec > 0 then
          minLength = 1
          if #spec >= 2 and string.endsWith(spec, ":") then
            if string.startsWith(spec, ":") then
              columnAlignments[column] = "center"
              minLength = 3
            else
              columnAlignments[column] = "right"
              minLength = 2
            end
          else
            columnAlignments[column] = "left"
          end

          if minLength > (columnLengths[column] or 0) then
            columnLengths[column] = minLength
          end
          column = column + 1
        end
      end
    elseif child.type == "TableHeader" or child.type == "TableRow" then
      column = 1
      for i, cell in ipairs(child.children) do
        if cell.type == "TableDelimiter" then
          next = child.children[i + 1]
          if next and next.type == "TableCell" then
            len = next.to - next.from
            if len > (columnLengths[column] or 0) then
              columnLengths[column] = len
            end
          end
         
          column = column + 1
        end
      end
    end
  end
  print(columnAlignments)

  -- Then print the table using these column lengths
  output = ""
  for _, child in ipairs(tree.children) do
    if child.type == "TableDelimiter" then
      output = output .. "\n|"
      for column, len in ipairs(columnLengths) do
        align = columnAlignments[column]
        if align == "left" then
          -- This is separate case because could be shorter than 2 characters
          output = output .. string.rep("-", len)
        else
          output = output .. (align == "center" and ":" or "-") .. string.rep("-", len - 2) .. ":"
        end
        output = output .. "|"
      end
    elseif child.type == "TableHeader" or child.type == "TableRow" then
      if child.type ~= "TableHeader" then
        output = output .. "\n"
      end
      output = output .. "|"
      column = 1
      for i, cell in ipairs(child.children) do
        if cell.type == "TableDelimiter" then
          next = child.children[i + 1]
          if next and next.type == "TableCell" then
            len = next.to - next.from
           
            -- Similar to plugs/index/table.ts:concatChildrenTexts
            for _, next_child in ipairs(next.children) do
              output = output .. next_child.text
            end
           
            output = output .. string.rep(" ", columnLengths[column] - len) .. "|"
          end
         
          column = column + 1
        end
      end
    end
  end

  -- Replace the table node with the formatted content
  editor.replaceRange(tree.from, tree.to, output)
end